├── .gitignore ├── LICENSE ├── README.md ├── Run.command └── Scripts ├── WebDriver.py ├── bdmesg.py ├── downloader.py ├── plist.py ├── run.py └── utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # Ignore output folder 7 | Web Drivers 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Ignore hidden files 13 | .* 14 | 15 | # Distribution / packaging 16 | .Python 17 | build/ 18 | develop-eggs/ 19 | dist/ 20 | downloads/ 21 | eggs/ 22 | .eggs/ 23 | lib/ 24 | lib64/ 25 | parts/ 26 | sdist/ 27 | var/ 28 | wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | MANIFEST 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # pyenv 82 | .python-version 83 | 84 | # celery beat schedule file 85 | celerybeat-schedule 86 | 87 | # SageMath parsed files 88 | *.sage.py 89 | 90 | # Environments 91 | .env 92 | .venv 93 | env/ 94 | venv/ 95 | ENV/ 96 | env.bak/ 97 | venv.bak/ 98 | 99 | # Spyder project settings 100 | .spyderproject 101 | .spyproject 102 | 103 | # Rope project settings 104 | .ropeproject 105 | 106 | # mkdocs documentation 107 | /site 108 | 109 | # mypy 110 | .mypy_cache/ 111 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 CorpNewt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Web-Driver-Toolkit 2 | A small python script for downloading NVIDIA Web Drivers based on either the current OS build version - or a selected build version. 3 | 4 | Can also patch the currently installed web drivers, as well as web driver install packages. 5 | 6 | To install, do the following one line at a time in Terminal: 7 | 8 | git clone https://github.com/corpnewt/Web-Driver-Toolkit 9 | cd Web-Driver-Toolkit 10 | chmod +x Run.command 11 | 12 | Then run with either `./Run.command` or by double-clicking *Run.command* 13 | 14 | ## Thanks To: 15 | 16 | * Slice, apianti, vit9696, Download Fritz, Zenith432, STLVNUB, JrCs,cecekpawon, Needy, cvad, Rehabman, philip_petev, ErmaC and the rest of the Clover crew for Clover and bdmesg 17 | -------------------------------------------------------------------------------- /Run.command: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | py="$( which python )" 4 | py3="$( which python3 )" 5 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)" 6 | python_install="" 7 | script_name="Scripts/WebDriver.py" 8 | arg="$1" 9 | run_root="No" 10 | 11 | function main () { 12 | if [[ "$py3" == "" ]]; then 13 | # No python 3 14 | if [[ "$py" == "" ]]; then 15 | # No python 2 either 16 | echo "Python is not installed!" 17 | exit 1 18 | else 19 | python_install=$py 20 | fi 21 | else 22 | python_install=$py3 23 | fi 24 | 25 | if [[ "$arg" != "" ]]; then 26 | # We got an arg passed - try to launch it 27 | "$python_install" "$arg" 28 | else 29 | "$python_install" "$DIR"/"$script_name" 30 | fi 31 | } 32 | 33 | function checkRoot () { 34 | if [[ "$(whoami)" != "root" ]]; then 35 | clear 36 | echo This script requires root privileges. 37 | echo Please enter your admin password to continue. 38 | echo 39 | sudo "$0" "$1" "$installerName" "$installerVersion" 40 | exit $? 41 | fi 42 | } 43 | 44 | if [[ "$run_root" == "Yes" ]]; then 45 | checkRoot 46 | fi 47 | main -------------------------------------------------------------------------------- /Scripts/WebDriver.py: -------------------------------------------------------------------------------- 1 | import sys, os, tempfile, shutil, re, base64, binascii, time, bdmesg, downloader, plist, run, utils 2 | 3 | class WebDriver: 4 | 5 | def __init__(self): 6 | 7 | # Check the OS first 8 | if not str(sys.platform) == "darwin": 9 | self.u.head("Incompatible System") 10 | print(" ") 11 | print("This script can only be run from macOS/OS X.") 12 | print(" ") 13 | print("The current running system is \"{}\".".format(sys.platform)) 14 | print(" ") 15 | self.grab("Press [enter] to quit...") 16 | print(" ") 17 | exit(1) 18 | 19 | self.dl = downloader.Downloader() 20 | self.r = run.Run() 21 | self.u = utils.Utils() 22 | self.web_drivers = None 23 | self.os_build_number = None 24 | self.os_number = None 25 | self.wd_loc = None 26 | self.sip_checked = False 27 | self.installed_version = "Not Installed!" 28 | self.get_manifest() 29 | self.get_system_info() 30 | 31 | def _check_info(self): 32 | if os.path.exists("/System/Library/Extensions/NVDAStartupWeb.kext"): 33 | self.wd_loc = "/System/Library/Extensions/NVDAStartupWeb.kext" 34 | elif os.path.exists("/Library/Extensions/NVDAStartupWeb.kext"): 35 | self.wd_loc = "/Library/Extensions/NVDAStartupWeb.kext" 36 | else: 37 | self.wd_loc = None 38 | 39 | def check_sip(self): 40 | # Checks our sip status and warns if needed 41 | sip_stats = self.r.run({"args" : ["csrutil", "status"]})[0] 42 | msg = "Unknown SIP Configuration!\n" 43 | title = "Unknown" 44 | if not sip_stats.startswith("System Integrity Protection status:"): 45 | # Error getting SIP status 46 | return None 47 | if sip_stats.startswith("System Integrity Protection status: disabled."): 48 | # SIP is disabled - return true to imply we have the "go ahead" 49 | return True 50 | if sip_stats.startswith("System Integrity Protection status: enabled (Custom Configuration)."): 51 | # SIP is partially enabled - determine if fs protection and kext signing is disabled 52 | if "Filesystem Protections: disabled" in sip_stats and "Kext Signing: disabled" in sip_stats: 53 | # Still good - let's roll 54 | return True 55 | title = "Partially Disabled" 56 | msg = "SIP is only partially disabled!\nKext signing and/or fs protection are eanbled!\n" 57 | 58 | if sip_stats == "System Integrity Protection status: enabled.": 59 | # SIP is enabled completely 60 | title = "Enabled" 61 | msg = "System Integrity Protection is completely enabled!\n" 62 | self.u.head("SIP Is " + title) 63 | print(" ") 64 | print(msg) 65 | print("This may prevent the web drivers from being patched or loading.") 66 | print(" ") 67 | menu = self.grab("Would you like to continue? (y/n): ") 68 | 69 | if not len(menu): 70 | return self.check_sip() 71 | 72 | if menu[:1].lower() == "n": 73 | return False 74 | elif menu[:1].lower() == "y": 75 | return True 76 | 77 | return self.check_sip() 78 | 79 | def check_path(self, path): 80 | # Loop until we either get a working path - or no changes 81 | count = 0 82 | while count < 100: 83 | count += 1 84 | if not len(path): 85 | # We uh.. stripped out everything - bail 86 | return None 87 | if os.path.exists(path): 88 | # Exists! 89 | return os.path.abspath(path) 90 | # Check quotes first 91 | if (path[0] == '"' and path[-1] == '"') or (path[0] == "'" and path[-1] == "'"): 92 | path = path[1:-1] 93 | continue 94 | # Check for tilde 95 | if path[0] == "~": 96 | test_path = os.path.expanduser(path) 97 | if test_path != path: 98 | # We got a change 99 | path = test_path 100 | continue 101 | # If we have no spaces to trim - bail 102 | if not (path[0] == " " or path[0] == " ") and not(path[-1] == " " or path[-1] == " "): 103 | return None 104 | # Here we try stripping spaces/tabs 105 | test_path = path 106 | t_count = 0 107 | while t_count < 100: 108 | t_count += 1 109 | t_path = test_path 110 | while len(t_path): 111 | if os.path.exists(t_path): 112 | return os.path.abspath(t_path) 113 | if t_path[-1] == " " or t_path[-1] == " ": 114 | t_path = t_path[:-1] 115 | continue 116 | break 117 | if test_path[0] == " " or test_path[0] == " ": 118 | test_path = test_path[1:] 119 | continue 120 | break 121 | # Escapes! 122 | test_path = "\\".join([x.replace("\\", "") for x in path.split("\\\\")]) 123 | if test_path != path and not (path[0] == " " or path[0] == " "): 124 | path = test_path 125 | continue 126 | if path[0] == " " or path[0] == " ": 127 | path = path[1:] 128 | return None 129 | 130 | # Helper methods 131 | def grab(self, prompt): 132 | if sys.version_info >= (3, 0): 133 | return input(prompt) 134 | else: 135 | return str(raw_input(prompt)) 136 | 137 | # Header drawing method 138 | def head(self, text = "Web Driver Toolkit", width = 50): 139 | os.system("clear") 140 | print(" {}".format("#"*width)) 141 | mid_len = int(round(width/2-len(text)/2)-2) 142 | middle = " #{}{}{}#".format(" "*mid_len, text, " "*((width - mid_len - len(text))-2)) 143 | print(middle) 144 | print("#"*width) 145 | 146 | def custom_quit(self): 147 | self.u.head("Web Driver Toolkit") 148 | print("by CorpNewt\n") 149 | print("Thanks for testing it out, for bugs/comments/complaints") 150 | print("send me a message on Reddit, or check out my GitHub:\n") 151 | print("www.reddit.com/u/corpnewt") 152 | print("www.github.com/corpnewt\n") 153 | print("Have a nice day/night!\n\n") 154 | exit(0) 155 | 156 | def get_manifest(self): 157 | self.u.head("Retrieving Manifest...") 158 | print(" ") 159 | print("Retrieving manifest from \"https://gfe.nvidia.com/mac-update\"...\n") 160 | try: 161 | plist_data = self.dl.get_bytes("https://gfe.nvidia.com/mac-update") 162 | if not plist_data or not len(str(plist_data)): 163 | print("Looks like that site isn't responding!\n\nPlease check your intenet connection and try again.") 164 | self.u.grab("",timeout=3) 165 | self.web_drivers = {} 166 | return 167 | self.web_drivers = plist.loads(plist_data) 168 | except: 169 | print("Something went wrong while getting the manifest!\n\nPlease check your intenet connection and try again.") 170 | self.u.grab("",timeout=3) 171 | self.web_drivers = {} 172 | 173 | def get_system_info(self): 174 | self.installed_version = "Not Installed!" 175 | self.os_build_number = self.r.run({"args" : ["sw_vers", "-buildVersion"]})[0].strip() 176 | self.os_number = self.r.run({"args" : ["sw_vers", "-productVersion"]})[0].strip() 177 | if self.wd_loc: 178 | info_plist = plist.readPlist(self.wd_loc + "/Contents/Info.plist") 179 | self.installed_version = info_plist["CFBundleGetInfoString"].split(" ")[-1].replace("(", "").replace(")", "") 180 | 181 | def check_dir(self, build): 182 | os.chdir(os.path.dirname(os.path.realpath(__file__))) 183 | os.chdir("../") 184 | if not os.path.exists("Web Drivers"): 185 | os.mkdir("Web Drivers") 186 | os.chdir("Web Drivers") 187 | if not os.path.exists(build): 188 | os.mkdir(build) 189 | os.chdir(build) 190 | return os.getcwd() 191 | 192 | def download_for_build(self, build): 193 | self.u.head("Downloading for " + build) 194 | print(" ") 195 | dl_update = None 196 | if not "updates" in self.web_drivers: 197 | print("The manifest was unreachable!\n\nPlease check your internet connection and update the manifest.") 198 | self.u.grab("",timeout=5) 199 | return 200 | for update in self.web_drivers.get("updates", []): 201 | if update["OS"].lower() == build.lower(): 202 | dl_update = update 203 | break 204 | if not dl_update: 205 | print("There isn't a version available for that build number!") 206 | self.u.grab("",timeout=5) 207 | return 208 | print("Downloading " + dl_update["version"]) 209 | print(" ") 210 | # Get the OS version + build number 10.xx.x (xxAxxxx) 211 | folder_name = "{} ({})".format(self.get_os(build), build) 212 | # self.check_dir(build) 213 | self.check_dir(folder_name) 214 | dl_file = self.dl.stream_to_file(dl_update["downloadURL"], dl_update["downloadURL"].split("/")[-1]) 215 | if dl_file: 216 | print(dl_file + " downloaded successfully!") 217 | self.r.run({"args":["open", os.getcwd()]}) 218 | self.u.grab("",timeout=5) 219 | 220 | def format_table(self, items, columns): 221 | max_length = 0 222 | current_row = 0 223 | row_list = [[]] 224 | cur_list = [] 225 | msg = "" 226 | # Let's break things up and give a numerical value 227 | alpha = "abcdefghijklmnopqrstuvwxyz" 228 | new_items = [] 229 | for i in items: 230 | new_items.append({"build" : i, "value" : self.get_value(i)}) 231 | 232 | # Sort by numeric value instead of general build number 233 | # This is helpful in cases where 17B48 comes before 17B1002 234 | # even though 4 > 1 235 | sorted_list = sorted(new_items, key=lambda x:x["value"]) 236 | for key in sorted_list: 237 | entry = key["build"] 238 | if len(entry) > max_length: 239 | max_length = len(entry) 240 | row_list[len(row_list)-1].append(entry) 241 | if len(row_list[len(row_list)-1]) >= columns: 242 | row_list.append([]) 243 | current_row += 1 244 | for row in row_list: 245 | for entry in row: 246 | entry = entry.ljust(max_length) 247 | msg += entry + " " 248 | msg += "\n" 249 | return msg 250 | 251 | def get_os(self, build_number): 252 | # Returns the best-guess OS version for the build number 253 | alpha = "abcdefghijklmnopqrstuvwxyz" 254 | os_version = "Unknown" 255 | major = minor = "" 256 | try: 257 | # Formula looks like this: AAB; AA - 4 = 10.## version 258 | # B index in "ABCDEFGHIJKLMNOPQRSTUVXYZ" = 10.##.## version 259 | split = re.findall(r"[^\W\d_]+|\d+", build_number) 260 | major = int(split[0])-4 261 | minor = alpha.index(split[1].lower()) 262 | os_version = "10.{}.{}".format(major, minor) 263 | except: 264 | pass 265 | return os_version 266 | 267 | def get_value(self, build_number): 268 | alpha = "abcdefghijklmnopqrstuvwxyz" 269 | # Split them up 270 | split = re.findall(r"[^\W\d_]+|\d+", build_number) 271 | start = split[0].rjust(4, "0") 272 | alph = split[1] 273 | end = split[2].rjust(6, "0") 274 | alpha_num = str(alpha.index(alph.lower())).rjust(2, "0") 275 | return int(start + alpha_num + end) 276 | 277 | def build_search(self): 278 | self.u.head("Web Drivers Search") 279 | print(" ") 280 | if not "updates" in self.web_drivers: 281 | # No manifest 282 | print("The manifest was unreachable!\n\nPlease check your internet connection and update the manifest.") 283 | self.u.grab("",timeout=5) 284 | return 285 | print("OS Version: {}".format(self.os_number)) 286 | print("OS Build Number: {}".format(self.os_build_number)) 287 | print(" ") 288 | print("M. Main Menu") 289 | print("Q. Quit") 290 | print(" ") 291 | menu = self.grab("Please type the build number of or OS version\nof your target web driver: ") 292 | 293 | if not len(menu): 294 | self.build_search() 295 | return 296 | 297 | if menu[:1].lower() == "m": 298 | return 299 | elif menu[:1].lower() == "q": 300 | self.custom_quit() 301 | 302 | # At this point, we have a build to search for 303 | mwd = next((x for x in self.web_drivers.get("updates", []) if x["OS"].lower() == menu.lower()), None) 304 | if mwd: 305 | # Found it - download it! 306 | self.download_for_build(mwd["OS"]) 307 | return 308 | # We didn't get an exact match, let's try to determine what's up 309 | # First check if it's a build number (##N####) or OS number (10.##.##) 310 | p = menu.split(".") 311 | p = [x for x in p if x != ""] 312 | if len(p) < 2 or len(p) > 3: 313 | # Nothing found - try again! 314 | self.build_search() 315 | return 316 | # We have . separated stuffs 317 | wd_list = [x for x in self.web_drivers.get("updates", []) if self.get_os(x["OS"]).startswith(menu)] 318 | if len(wd_list) == 0: 319 | # No matches 320 | self.u.head("Searching For {}".format(menu)) 321 | print(" ") 322 | print("No matches found!") 323 | print(" ") 324 | self.grab("Press [enter] to return to the search menu...") 325 | self.build_search() 326 | return 327 | else: 328 | # We got at least one match 329 | m_title = menu 330 | while True: 331 | self.u.head("Matches For {}".format(m_title)) 332 | print(" ") 333 | index = 0 334 | for i in wd_list: 335 | index += 1 336 | print("{}. {} ({}) - {}".format(index, self.get_os(i["OS"]), i["OS"], i["version"])) 337 | print(" ") 338 | print("S. Return to Search") 339 | print("M. Main Menu") 340 | print("Q. Quit") 341 | print(" ") 342 | menu = self.grab("Please select a build to download: ") 343 | if not len(menu): 344 | continue 345 | if menu[:1].lower() == "s": 346 | self.build_search() 347 | return 348 | elif menu[:1].lower() == "m": 349 | return 350 | elif menu[:1].lower() == "q": 351 | self.custom_quit() 352 | try: 353 | menu = int(menu) -1 354 | except: 355 | continue 356 | if menu < 0 or menu >= len(wd_list): 357 | continue 358 | self.download_for_build(wd_list[menu]["OS"]) 359 | return 360 | 361 | 362 | def build_list(self): 363 | # Print 8 columns 364 | self.u.head("Web Drivers By Build Number") 365 | print(" ") 366 | build_list = [] 367 | if not "updates" in self.web_drivers: 368 | # No manifest 369 | print("The manifest was unreachable!\n\nPlease check your internet connection and update the manifest.") 370 | self.u.grab("",timeout=5) 371 | return 372 | for update in self.web_drivers.get("updates", []): 373 | build_list.append(update["OS"]) 374 | 375 | print("OS Build Number: {}".format(self.os_build_number)) 376 | print(" ") 377 | 378 | print("Available Build Numbers:\n") 379 | builds = self.format_table(build_list, 8) 380 | print(builds) 381 | print("M. Main Menu") 382 | print("Q. Quit") 383 | print(" ") 384 | menu = self.grab("Please type a build number to download the web driver: ") 385 | 386 | if not len(menu): 387 | self.build_list() 388 | return 389 | 390 | if menu[:1].lower() == "m": 391 | return 392 | elif menu[:1].lower() == "q": 393 | self.custom_quit() 394 | 395 | for build in build_list: 396 | if build.lower() == menu.lower(): 397 | self.download_for_build(build) 398 | return 399 | self.build_list() 400 | 401 | def patch_menu(self): 402 | self.u.head("Web Driver Patch") 403 | print(" ") 404 | os.chdir(os.path.dirname(os.path.realpath(__file__))) 405 | 406 | if not self.wd_loc: 407 | print("NVDAStartupWeb.kext was not found in either /L/E or /S/L/E!\n") 408 | print("Please make sure you have the Web Drivers installed.") 409 | self.u.grab("",timeout=5) 410 | return 411 | info_plist = plist.readPlist(self.wd_loc + "/Contents/Info.plist") 412 | current_build = info_plist.get("IOKitPersonalities", {}).get("NVDAStartup", {}).get("NVDARequiredOS", None) 413 | 414 | print("OS Build Number: {}".format(self.os_build_number)) 415 | print("WD Target Build: {}".format(current_build)) 416 | 417 | print(" ") 418 | print("C. Set to Current Build Number") 419 | print("I. Input New Build Number") 420 | can_restore = False 421 | if os.path.exists(self.wd_loc + "/Contents/Info.plist.bak"): 422 | print("R. Restore Backup") 423 | print("D. Delete Backup") 424 | can_restore = True 425 | print(" ") 426 | print("M. Main Menu") 427 | print("Q. Quit") 428 | print(" ") 429 | 430 | menu = self.grab("Please make a selection: ") 431 | 432 | if not len(menu): 433 | self.patch_menu() 434 | return 435 | 436 | if menu[:1].lower() == "q": 437 | self.custom_quit() 438 | elif menu[:1].lower() == "c": 439 | self.set_build(self.os_build_number) 440 | elif menu[:1].lower() == "i": 441 | self.custom_build() 442 | elif menu[:1].lower() == "r" and can_restore: 443 | self.restore_backup() 444 | elif menu[:1].lower() == "d" and can_restore: 445 | self.delete_backup() 446 | elif menu[:1].lower() == "m": 447 | return 448 | 449 | self.patch_menu() 450 | return 451 | 452 | def restore_backup(self): 453 | if not self.sip_checked: 454 | res = self.check_sip() 455 | if res == None or res == True: 456 | # Likely on Yosemite? 457 | self.sip_checked = True 458 | else: 459 | return 460 | 461 | self.u.head("Restoring Backup Info.plist") 462 | print(" ") 463 | if not os.path.exists(self.wd_loc + "/Contents/Info.plist.bak"): 464 | # Create a backup 465 | print("Backup doesn't exist...") 466 | self.u.grab("",timeout=5) 467 | return 468 | # Doing things 469 | c = [ 470 | { 471 | "args" : ["rm", self.wd_loc + "/Contents/Info.plist"], 472 | "sudo" : True, 473 | "message" : "Removing " + self.wd_loc + "/Contents/Info.plist...\n" 474 | }, 475 | { 476 | "args" : ["mv", "-f", self.wd_loc + "/Contents/Info.plist.bak", self.wd_loc + "/Contents/Info.plist"], 477 | "sudo" : True, 478 | "message" : "Renaming Info.plist.bak to Info.plist...\n" 479 | }, 480 | { 481 | "args" : ["chown", "0:0", self.wd_loc + "/Contents/Info.plist"], 482 | "sudo" : True, 483 | "message" : "Updating ownership and permissions...\n" 484 | }, 485 | { 486 | "args" : ["chmod", "755", self.wd_loc + "/Contents/Info.plist"], 487 | "sudo" : True 488 | }, 489 | { 490 | "args" : ["kextcache", "-i", "/"], 491 | "sudo" : True, 492 | "stream" : True, 493 | "message" : "Rebuilding kext cache...\n" 494 | }, 495 | { 496 | "args" : ["kextcache", "-u", "/"], 497 | "sudo" : True, 498 | "stream" : True, 499 | } 500 | ] 501 | self.r.run(c, True) 502 | print(" ") 503 | print("Done.") 504 | self.u.grab("",timeout=5) 505 | return 506 | 507 | def delete_backup(self): 508 | self.u.head("Deleting Backup Info.plist") 509 | print(" ") 510 | if not os.path.exists(self.wd_loc + "/Contents/Info.plist.bak"): 511 | # Create a backup 512 | print("Backup doesn't exist...") 513 | self.u.grab("",timeout=5) 514 | return 515 | # Removing 516 | print("Removing " + self.wd_loc + "/Contents/Info.plist.bak...\n") 517 | self.r.run({"args":["rm", self.wd_loc + "/Contents/Info.plist.bak"],"sudo":True}) 518 | print("Done.") 519 | self.u.grab("",timeout=5) 520 | return 521 | 522 | def set_build(self, build_number): 523 | if not self.sip_checked: 524 | res = self.check_sip() 525 | if res == None or res == True: 526 | # Likely on Yosemite? 527 | self.sip_checked = True 528 | else: 529 | return 530 | 531 | self.u.head("Setting NVDARequiredOS to {}".format(build_number)) 532 | print(" ") 533 | os.chdir(os.path.dirname(os.path.realpath(__file__))) 534 | 535 | # Start our command list 536 | c = [] 537 | 538 | info_plist = plist.readPlist(self.wd_loc + "/Contents/Info.plist") 539 | if not os.path.exists(self.wd_loc + "/Contents/Info.plist.bak"): 540 | # Create a backup 541 | self.r.run({ 542 | "args" : ["cp", self.wd_loc + "/Contents/Info.plist", self.wd_loc + "/Contents/Info.plist.bak"], 543 | "sudo" : True, 544 | "message" : "Creating backup...\n" 545 | }) 546 | # plist.writePlist(info_plist, self.wd_loc + "/Contents/Info.plist.bak") 547 | # Change the build number and write to the main plist 548 | print("Patching plist for build \"{}\"...\n".format(build_number)) 549 | info_plist["IOKitPersonalities"]["NVDAStartup"]["NVDARequiredOS"] = build_number 550 | # Make a temp folder for our plist 551 | temp_folder = tempfile.mkdtemp() 552 | # Write the changes 553 | plist.writePlist(info_plist, temp_folder + "/Info.plist") 554 | # Build and run commands 555 | c = [ 556 | { 557 | "args" : ["mv", "-f", temp_folder + "/Info.plist", self.wd_loc + "/Contents/Info.plist"], 558 | "sudo" : True 559 | }, 560 | { 561 | "args" : ["chown", "0:0", self.wd_loc + "/Contents/Info.plist"], 562 | "sudo" : True, 563 | "message" : "Updating ownership and permissions...\n" 564 | }, 565 | { 566 | "args" : ["chmod", "755", self.wd_loc + "/Contents/Info.plist"], 567 | "sudo" : True 568 | }, 569 | { 570 | "args" : ["kextcache", "-i", "/"], 571 | "sudo" : True, 572 | "stream" : True, 573 | "message" : "Rebuilding kext cache...\n" 574 | }, 575 | { 576 | "args" : ["kextcache", "-u", "/"], 577 | "sudo" : True, 578 | "stream" : True, 579 | } 580 | ] 581 | self.r.run(c, True) 582 | # Remove temp 583 | if os.path.exists(temp_folder): 584 | shutil.rmtree(temp_folder) 585 | print(" ") 586 | print("Done.") 587 | self.u.grab("",timeout=5) 588 | return 589 | 590 | def custom_build(self): 591 | self.u.head("Custom Build") 592 | print(" ") 593 | print("") 594 | 595 | if not self.wd_loc: 596 | print("NVDAStartupWeb.kext was not found in either /L/E or /S/L/E!\n") 597 | print("Please make sure you have the Web Drivers installed.") 598 | self.u.grab("",timeout=5) 599 | return 600 | info_plist = plist.readPlist(self.wd_loc + "/Contents/Info.plist") 601 | current_build = info_plist.get("IOKitPersonalities", {}).get("NVDAStartup", {}).get("NVDARequiredOS", None) 602 | 603 | print("OS Build Number: {}".format(self.os_build_number)) 604 | print("WD Target Build: {}".format(current_build)) 605 | 606 | print(" ") 607 | print("P. Patch Menu") 608 | print(" ") 609 | print("M. Main Menu") 610 | print("Q. Quit") 611 | print(" ") 612 | 613 | menu = self.grab("Please enter a new build number: ") 614 | 615 | if not len(menu): 616 | self.custom_build() 617 | return 618 | 619 | if menu.lower() == "q": 620 | self.custom_quit() 621 | elif menu.lower() == "m": 622 | self.main() 623 | elif menu.lower() == "p": 624 | return 625 | 626 | # We have a build number 627 | self.set_build(menu) 628 | self.main() 629 | return 630 | 631 | def patch_installer_build(self): 632 | self.u.head("Patch Install Package") 633 | print(" ") 634 | print("OS Build Number: {}".format(self.os_build_number)) 635 | print(" ") 636 | print("M. Main Menu") 637 | print("Q. Quit") 638 | print(" ") 639 | menu = self.grab("Would you like to also patch the web drivers inside the package? (y/n): ") 640 | 641 | if not len(menu): 642 | self.patch_installer_build() 643 | return 644 | 645 | if menu.lower() == "q": 646 | self.custom_quit() 647 | elif menu.lower() == "m": 648 | return 649 | elif menu.lower() == "n": 650 | self.patch_installer() 651 | return 652 | 653 | if not menu.lower() == "y": 654 | self.patch_installer_build() 655 | return 656 | 657 | while True: 658 | # Get a build number 659 | self.u.head("Patch Install Package") 660 | print(" ") 661 | print("OS Build Number: {}".format(self.os_build_number)) 662 | print(" ") 663 | print("C. Set To Current Build Number") 664 | print("M. Main Menu") 665 | print("Q. Quit") 666 | print(" ") 667 | menu = self.grab("Please enter the target build number: ") 668 | if not len(menu): 669 | continue 670 | if menu.lower() == "m": 671 | return 672 | elif menu.lower() == "q": 673 | self.custom_quit() 674 | elif menu.lower() == "c": 675 | self.patch_installer(self.os_build_number) 676 | return 677 | else: 678 | self.patch_installer(menu) 679 | return 680 | 681 | def patch_installer(self, build = None): 682 | if build: 683 | self.u.head("Patch Install Package ({})".format(build)) 684 | else: 685 | self.u.head("Patch Install Package") 686 | print(" ") 687 | print("OS Build Number: {}".format(self.os_build_number)) 688 | if build: 689 | print("WD Target Build: {}".format(build)) 690 | print(" ") 691 | print("M. Main Menu") 692 | print("Q. Quit") 693 | print(" ") 694 | menu = self.grab("Please drag and drop the install package to patch: ") 695 | 696 | if not len(menu): 697 | self.patch_installer(build) 698 | return 699 | 700 | if menu.lower() == "q": 701 | self.custom_quit() 702 | elif menu.lower() == "m": 703 | return 704 | 705 | # Check path 706 | menu_path = self.check_path(menu) 707 | if not menu_path: 708 | print("That path doesn't exist...") 709 | self.u.grab("",timeout=3) 710 | self.patch_installer(build) 711 | return 712 | # Path exists 713 | temp_dir = tempfile.mkdtemp() 714 | try: 715 | self.patch_pkg(menu_path, temp_dir, build) 716 | except: 717 | print("Something went wrong!") 718 | self.u.grab("",timeout=3) 719 | os.chdir(os.path.dirname(os.path.realpath(__file__))) 720 | if os.path.exists(temp_dir): 721 | shutil.rmtree(temp_dir) 722 | return 723 | 724 | def patch_pkg(self, package, temp, build = None): 725 | self.u.head("Patching Install Package") 726 | print(" ") 727 | script_path = os.path.dirname(os.path.realpath(__file__)) 728 | print("Expanding package...\n") 729 | stat = self.r.run({"args" : ["pkgutil", "--expand", package, temp + "/package"]}) 730 | if not stat[2] == 0: 731 | print("Something went wrong!\n") 732 | print(stat[1]) 733 | return 734 | new_dist = "" 735 | print("Patching Distribution...\n") 736 | with open(temp + "/package/Distribution") as f: 737 | for line in f: 738 | if "if (!validatesoftware())" in line.lower(): 739 | continue 740 | if "if (!validatehardware())" in line.lower(): 741 | continue 742 | if "return false;" in line: 743 | line = line.replace("return false;", "return true;") 744 | new_dist += line 745 | with open(temp + "/package/Distribution", "w") as f: 746 | f.write(new_dist) 747 | 748 | if build: 749 | # We have a build - let's do stuff 750 | print("Patching Kext for {}...".format(build)) 751 | os.chdir(temp + "/package") 752 | os.chdir(self.r.run({"args" : "ls | grep -i nvwebdrivers", "shell" : True})[0].strip()) 753 | self.r.run({"args" : ["mkdir", "temp"]}) 754 | os.chdir("temp") 755 | print(" Extracting Payload...") 756 | self.r.run({"args" : ["tar", "xvf", "../Payload"]}) 757 | info_path = None 758 | if os.path.exists("./NVDAStartupWeb.kext/Contents/Info.plist"): 759 | info_path = "./NVDAStartupWeb.kext/Contents/Info.plist" 760 | elif os.path.exists("./Library/Extensions/NVDAStartupWeb.kext/Contents/Info.plist"): 761 | info_path = "./Library/Extensions/NVDAStartupWeb.kext/Contents/Info.plist" 762 | elif os.path.exists("./NVDAStartup.kext/Contents/Info.plist"): 763 | # Old stuff... 764 | info_path = "./NVDAStartup.kext/Contents/Info.plist" 765 | if not info_path: 766 | print("Couldn't find Info.plist to patch!") 767 | return 768 | print(" Patching Info.plist for {}...".format(build)) 769 | # Got the info.plist - load it and patch it 770 | info_plist = plist.readPlist(os.path.realpath(info_path)) 771 | info_plist["IOKitPersonalities"]["NVDAStartup"]["NVDARequiredOS"] = build 772 | # Write the changes 773 | plist.writePlist(info_plist, os.path.realpath(info_path)) 774 | # Remove the old Payload and BOM 775 | print(" Removing old Payload and BOM...") 776 | self.r.run({"args" : ["rm", "../Payload"]}) 777 | self.r.run({"args" : ["rm", "../BOM"]}) 778 | # Repacking Payload 779 | print(" Setting ownership...") 780 | stat = self.r.run({"args" : "sudo chown -R 0:0 ./*", "shell" : True}) 781 | if not stat[2] == 0: 782 | print("Something went wrong!\n") 783 | print(stat[1]) 784 | return 785 | print(" Repacking Payload...") 786 | stat = self.r.run({"args" : "sudo find . | sudo cpio -o --format odc | gzip -c > ../Payload", "shell" : True}) 787 | if not stat[2] == 0: 788 | print("Something went wrong!\n") 789 | print(stat[1]) 790 | return 791 | # Generate BOM 792 | print(" Generating BOM...") 793 | stat = self.r.run({"args" : ["mkbom", "../../", "../BOM"], "sudo" : True}) 794 | if not stat[2] == 0: 795 | print("Something went wrong!\n") 796 | print(stat[1]) 797 | return 798 | # Clean up the temp folder 799 | print(" Cleaning up...\n") 800 | os.chdir("../") 801 | stat = self.r.run({"args" : ["rm", "-rf","temp"], "sudo" : True}) 802 | if not stat[2] == 0: 803 | print("Something went wrong!\n") 804 | print(stat[1]) 805 | return 806 | 807 | self.check_dir("Patched") 808 | print("Repacking...\n") 809 | os.chdir(os.path.dirname(os.path.realpath(__file__))) 810 | os.chdir("../Web Drivers/Patched/") 811 | suffix = " (Patched).pkg" if build == None else " ({}).pkg".format(build) 812 | self.r.run({"args" : ["pkgutil", "--flatten", temp + "/package", os.getcwd() + "/" + os.path.basename(package)[:-4] + suffix]}) 813 | print("Done.") 814 | self.r.run({"args":["open", os.getcwd()]}) 815 | self.u.grab("",timeout=5) 816 | 817 | def remove_drivers(self): 818 | self.u.head("Removing Web Drivers") 819 | print(" ") 820 | print("Clearing web drivers from /S/L/E...\n") 821 | self.r.run({"args":["sudo", "rm", "-rf", "/System/Library/Extensions/GeForce*Web.*", "/System/Library/Extensions/NVDA*Web.kext"], "shell" : True}) 822 | print("Clearing web drivers from /L/E...\n") 823 | self.r.run({"args":["sudo", "rm", "-rf", "/Library/Extensions/GeForce*Web.kext", "/Library/Extensions/NVDA*Web.kext"], "shell" : True}) 824 | # Rebuild kextcache 825 | print("Rebuilding kext cache...\n") 826 | self.flush_cache() 827 | print(" ") 828 | print("Done.") 829 | self.u.grab("",timeout=5) 830 | 831 | def config_menu(self): 832 | self.u.head("Config.plist Patch Menu") 833 | print(" ") 834 | os.chdir(os.path.dirname(os.path.realpath(__file__))) 835 | if not self.wd_loc: 836 | print("NVDAStartupWeb.kext was not found in either /L/E or /S/L/E!\n") 837 | print("Please make sure you have the Web Drivers installed.") 838 | self.u.grab("",timeout=5) 839 | return 840 | info_plist = plist.readPlist(self.wd_loc + "/Contents/Info.plist") 841 | current_build = info_plist.get("IOKitPersonalities", {}).get("NVDAStartup", {}).get("NVDARequiredOS", None) 842 | 843 | print("OS Build Number: {}".format(self.os_build_number)) 844 | print("WD Target Build: {}".format(current_build)) 845 | print(" ") 846 | print("C. Current Build Number") 847 | print(" ") 848 | print("M. Main Menu") 849 | print("Q. Quit") 850 | print(" ") 851 | 852 | menu = self.grab("Please make a selection (or type a custom build number): ") 853 | 854 | if not len(menu): 855 | self.config_menu() 856 | return 857 | 858 | if menu[:1].lower() == "m": 859 | return 860 | elif menu[:1].lower() == "q": 861 | self.custom_quit() 862 | elif menu[:1].lower() == "c": 863 | self.type_menu(self.os_build_number) 864 | else: 865 | self.type_menu(menu) 866 | self.config_menu() 867 | return 868 | 869 | def type_menu(self, build): 870 | self.u.head("Config.plist Patch: {}".format(build)) 871 | print(" ") 872 | os.chdir(os.path.dirname(os.path.realpath(__file__))) 873 | if not self.wd_loc: 874 | print("NVDAStartupWeb.kext was not found in either /L/E or /S/L/E!\n") 875 | print("Please make sure you have the Web Drivers installed.") 876 | self.u.grab("",timeout=5) 877 | return 878 | info_plist = plist.readPlist(self.wd_loc + "/Contents/Info.plist") 879 | current_build = info_plist.get("IOKitPersonalities", {}).get("NVDAStartup", {}).get("NVDARequiredOS", None) 880 | if build == current_build: 881 | print("Both builds are the same - this defeats the purpose of the patch.") 882 | self.u.grab("",timeout=5) 883 | return 884 | print("B. Show Base64 Values") 885 | print("H. Show Hex Values") 886 | print("P. Show Plist Patch") 887 | print(" ") 888 | print("M. Main Menu") 889 | print("C. Config.plist Patch Menu") 890 | print("Q. Quit") 891 | print(" ") 892 | menu = self.grab("Please make a selection: ") 893 | 894 | if not len(menu): 895 | self.config_menu() 896 | return 897 | 898 | display_text = data_type = "" 899 | 900 | if menu[:1].lower() == "m": 901 | self.main() 902 | return 903 | elif menu[:1].lower() == "q": 904 | self.custom_quit() 905 | elif menu[:1].lower() == "b": 906 | data_type = "Base64" 907 | display_text += "Name: NVDAStartupWeb\n" 908 | display_text += "Disabled: False\n" 909 | display_text += "Find: {}\n".format(self.get_base(current_build)) 910 | display_text += "Replace: {}\n".format(self.get_base(build)) 911 | display_text += "InfoPlist: True" 912 | elif menu[:1].lower() == "h": 913 | data_type = "Hex" 914 | display_text += "Name: NVDAStartupWeb\n" 915 | display_text += "Disabled: False\n" 916 | display_text += "Find: {}\n".format(self.get_hex(current_build)) 917 | display_text += "Replace: {}\n".format(self.get_hex(build)) 918 | display_text += "InfoPlist: True" 919 | elif menu[:1].lower() == "p": 920 | # Get some plist wizardry 921 | data_type = "Plist" 922 | plist_dict = { 923 | "Name" : "NVDAStartupWeb", 924 | "Comment" : "Nvidia {} to {}".format(current_build, build), 925 | "InfoPlistPatch" : True, 926 | "Disabled" : False, 927 | "Find" : self.get_base(current_build), 928 | "Replace" : self.get_base(build) 929 | } 930 | plist_string = plist.dumps(plist_dict).decode("utf-8") 931 | # Trim the plist 932 | display_text = "\n".join(plist_string.split("\n")[3:-2]) 933 | 934 | if len(display_text): 935 | self.u.head("Config.plist Patch: {}".format(build)) 936 | print(" ") 937 | print("Your {} Data:\n".format(data_type)) 938 | print(display_text) 939 | print(" ") 940 | self.grab("Press [enter] to return...") 941 | self.type_menu(build) 942 | return 943 | 944 | def get_base(self, value): 945 | return base64.b64encode(value.encode("utf-8")).decode("utf-8") 946 | 947 | def get_hex(self, value): 948 | text = binascii.hexlify(value.encode("utf-8")).decode("utf-8") 949 | text_list = re.findall('........?', text) 950 | return " ".join(text_list) 951 | 952 | def flush_cache(self, p=False): 953 | if p: 954 | self.u.head("Rebuilding Kext Cache") 955 | print(" ") 956 | # Rebuild kextcache 957 | print("Rebuilding kext cache...\n") 958 | self.r.run({"args":["sudo", "kextcache", "-i", "/"],"stream":True}) 959 | self.r.run({"args":["sudo", "kextcache", "-u", "/"],"stream":True}) 960 | if p: 961 | print(" ") 962 | print("Done.") 963 | self.u.grab("",timeout=5) 964 | 965 | def main(self): 966 | self._check_info() 967 | self.get_system_info() 968 | self.u.head("Web Driver Toolkit") 969 | print(" ") 970 | os.chdir(os.path.dirname(os.path.realpath(__file__))) 971 | 972 | print("OS Version: {} - {}".format(self.os_number, self.os_build_number)) 973 | print("WD Version: " + self.installed_version) 974 | 975 | if self.wd_loc: 976 | info_plist = plist.readPlist(self.wd_loc + "/Contents/Info.plist") 977 | current_build = info_plist.get("IOKitPersonalities", {}).get("NVDAStartup", {}).get("NVDARequiredOS", None) 978 | print("WD Target Build: {}".format(current_build)) 979 | 980 | if not "updates" in self.web_drivers: 981 | newest_version = "Manifest not available!" 982 | else: 983 | newest_version = "None for this build number!" 984 | for update in self.web_drivers.get("updates", []): 985 | if update["OS"].lower() == self.os_build_number.lower(): 986 | newest_version = update["version"] 987 | break 988 | 989 | if self.installed_version.lower() == newest_version.lower(): 990 | print("Newest: " + newest_version + " (Current)") 991 | else: 992 | print("Newest: " + newest_version) 993 | 994 | try: 995 | nv = self.r.run({"args":"nvram -p | grep -i nvda_drv | cut -d$'\t' -f 2", "shell" : True})[0].strip("\n") 996 | except: 997 | nv = "" 998 | aptio_loaded = "Unknown" 999 | aptio = next((x for x in bdmesg.bdmesg().split("\n") if "aptiomemoryfix" in x.lower()), None) 1000 | if aptio: 1001 | aptio_loaded = "Loaded" if "success" in aptio.lower() else "Not Loaded" 1002 | 1003 | if nv in ["1","1%00"]: 1004 | print("Status: Enabled (AptioMemoryFix {})".format(aptio_loaded)) 1005 | else: 1006 | print("Status: Disabled - or Unknown (AptioMemoryFix {})".format(aptio_loaded)) 1007 | 1008 | print(" ") 1009 | patch = False 1010 | if self.wd_loc: 1011 | print("P. Patch Menu") 1012 | patch = True 1013 | print("I. Patch Install Package") 1014 | print("D. Download For Current") 1015 | print("B. Download By Build Number") 1016 | print("S. Search By Build/OS Number") 1017 | print("R. Remove Web Drivers") 1018 | print("U. Update Manifest") 1019 | print("C. Config.plist Patch Menu") 1020 | print("F. Flush Kext Cache") 1021 | print("N. Attempt to {} nvram_drv=1 in NVRAM".format("unset" if nv in ["1","1%00"] else "set")) 1022 | print("") 1023 | print("Q. Quit") 1024 | print(" ") 1025 | 1026 | menu = self.grab("Please make a selection (just press enter to reload): ") 1027 | 1028 | if not len(menu): 1029 | return 1030 | 1031 | if menu[:1].lower() == "q": 1032 | self.custom_quit() 1033 | elif menu[:1].lower() == "p" and patch: 1034 | self.patch_menu() 1035 | elif menu[:1].lower() == "d": 1036 | self.download_for_build(self.os_build_number) 1037 | elif menu[:1].lower() == "b": 1038 | self.build_list() 1039 | elif menu[:1].lower() == "s": 1040 | self.build_search() 1041 | elif menu[:1].lower() == "i": 1042 | self.patch_installer_build() 1043 | elif menu[:1].lower() == "u": 1044 | self.get_manifest() 1045 | elif menu[:1].lower() == "r": 1046 | self.remove_drivers() 1047 | elif menu[:1].lower() == "c": 1048 | self.config_menu() 1049 | elif menu[:1].lower() == "f": 1050 | self.flush_cache(True) 1051 | elif menu[:1].lower() == "n": 1052 | if nv in ["1","1%00"]: 1053 | # Unset 1054 | self.u.head("Unsetting nvda_drv=1") 1055 | print("") 1056 | print("Running:\n\nsudo nvram -d nvda_drv") 1057 | self.r.run({"args":["sudo", "nvram", "-d", "nvda_drv"],"stream":True}) 1058 | else: 1059 | # Set 1060 | self.u.head("Setting nvda_drv=1") 1061 | print("") 1062 | print("Running:\n\nsudo nvram nvda_drv=1") 1063 | self.r.run({"args":["sudo", "nvram", "nvda_drv=1"],"stream":True}) 1064 | print("") 1065 | print("Done.") 1066 | self.u.grab("",timeout=5) 1067 | 1068 | return 1069 | 1070 | wd = WebDriver() 1071 | 1072 | while True: 1073 | try: 1074 | wd.main() 1075 | except Exception as e: 1076 | print(e) 1077 | time.sleep(5) 1078 | -------------------------------------------------------------------------------- /Scripts/bdmesg.py: -------------------------------------------------------------------------------- 1 | import binascii, subprocess, sys 2 | 3 | def get_clover_uuid(): 4 | bd = bdmesg() 5 | if not len(bd): 6 | return "" 7 | # Get bdmesg output - then parse for SelfDevicePath 8 | if not "SelfDevicePath=" in bd: 9 | # Not found 10 | return "" 11 | try: 12 | # Split to just the contents of that line 13 | line = bd.split("SelfDevicePath=")[1].split("\n")[0] 14 | # Get the HD section 15 | hd = line.split("HD(")[1].split(")")[0] 16 | # Get the UUID 17 | uuid = hd.split(",")[2] 18 | return uuid 19 | except: 20 | pass 21 | return "" 22 | 23 | def get_oc_uuid(): 24 | p = subprocess.Popen(["nvram","4D1FDA02-38C7-4A6A-9CC6-4BCCA8B30102:boot-path"], shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 25 | oc, oe = p.communicate() 26 | oc = _decode(oc) 27 | try: 28 | path = oc.split("GPT,")[1].split(",")[0] 29 | except: 30 | path = "" 31 | return path 32 | 33 | def get_bootloader_uuid(): 34 | b_uuid = get_clover_uuid() 35 | if not b_uuid: 36 | b_uuid = get_oc_uuid() 37 | return b_uuid 38 | 39 | def bdmesg(just_clover = True): 40 | b = "" if just_clover else _bdmesg(["ioreg","-l","-p","IOService","-w0"]) 41 | if b == "": 42 | b = _bdmesg(["ioreg","-l","-p","IODeviceTree","-w0"]) 43 | return b 44 | 45 | def _decode(var): 46 | if sys.version_info >= (3,0) and isinstance(var, bytes): 47 | var = var.decode("utf-8","ignore") 48 | return var 49 | 50 | def _bdmesg(comm): 51 | # Runs ioreg -l -p IODeviceTree -w0 and searches for "boot-log" 52 | p = subprocess.Popen(comm, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 53 | bd, be = p.communicate() 54 | bd = _decode(bd) 55 | for line in bd.split("\n"): 56 | # We're just looking for the "boot-log" property, then we need to format it 57 | if not '"boot-log"' in line: 58 | # Skip it! 59 | continue 60 | # Must have found it - let's try to split it, then get the hex data and process it 61 | try: 62 | # Split it up, then convert from hex to ascii 63 | ascii_bytes = binascii.unhexlify(line.split("<")[1].split(">")[0].encode("utf-8")) 64 | ascii_bytes = _decode(ascii_bytes) 65 | return ascii_bytes 66 | except: 67 | # Failed to convert 68 | return "" 69 | # Didn't find it 70 | return "" 71 | -------------------------------------------------------------------------------- /Scripts/downloader.py: -------------------------------------------------------------------------------- 1 | import sys, os, time, ssl, gzip 2 | from io import BytesIO 3 | # Python-aware urllib stuff 4 | if sys.version_info >= (3, 0): 5 | from urllib.request import urlopen, Request 6 | else: 7 | # Import urllib2 to catch errors 8 | import urllib2 9 | from urllib2 import urlopen, Request 10 | 11 | class Downloader: 12 | 13 | def __init__(self,**kwargs): 14 | self.ua = kwargs.get("useragent",{"User-Agent":"Mozilla"}) 15 | self.chunk = 1048576 # 1024 x 1024 i.e. 1MiB 16 | if os.name=="nt": os.system("color") # Initialize cmd for ANSI escapes 17 | # Provide reasonable default logic to workaround macOS CA file handling 18 | cafile = ssl.get_default_verify_paths().openssl_cafile 19 | try: 20 | # If default OpenSSL CA file does not exist, use that from certifi 21 | if not os.path.exists(cafile): 22 | import certifi 23 | cafile = certifi.where() 24 | self.ssl_context = ssl.create_default_context(cafile=cafile) 25 | except: 26 | # None of the above worked, disable certificate verification for now 27 | self.ssl_context = ssl._create_unverified_context() 28 | return 29 | 30 | def _decode(self, value, encoding="utf-8", errors="ignore"): 31 | # Helper method to only decode if bytes type 32 | if sys.version_info >= (3,0) and isinstance(value, bytes): 33 | return value.decode(encoding,errors) 34 | return value 35 | 36 | def open_url(self, url, headers = None): 37 | # Fall back on the default ua if none provided 38 | headers = self.ua if headers is None else headers 39 | # Wrap up the try/except block so we don't have to do this for each function 40 | try: 41 | response = urlopen(Request(url, headers=headers), context=self.ssl_context) 42 | except Exception as e: 43 | # No fixing this - bail 44 | return None 45 | return response 46 | 47 | def get_size(self, size, suffix=None, use_1024=False, round_to=2, strip_zeroes=False): 48 | # size is the number of bytes 49 | # suffix is the target suffix to locate (B, KB, MB, etc) - if found 50 | # use_2014 denotes whether or not we display in MiB vs MB 51 | # round_to is the number of dedimal points to round our result to (0-15) 52 | # strip_zeroes denotes whether we strip out zeroes 53 | 54 | # Failsafe in case our size is unknown 55 | if size == -1: 56 | return "Unknown" 57 | # Get our suffixes based on use_1024 58 | ext = ["B","KiB","MiB","GiB","TiB","PiB"] if use_1024 else ["B","KB","MB","GB","TB","PB"] 59 | div = 1024 if use_1024 else 1000 60 | s = float(size) 61 | s_dict = {} # Initialize our dict 62 | # Iterate the ext list, and divide by 1000 or 1024 each time to setup the dict {ext:val} 63 | for e in ext: 64 | s_dict[e] = s 65 | s /= div 66 | # Get our suffix if provided - will be set to None if not found, or if started as None 67 | suffix = next((x for x in ext if x.lower() == suffix.lower()),None) if suffix else suffix 68 | # Get the largest value that's still over 1 69 | biggest = suffix if suffix else next((x for x in ext[::-1] if s_dict[x] >= 1), "B") 70 | # Determine our rounding approach - first make sure it's an int; default to 2 on error 71 | try:round_to=int(round_to) 72 | except:round_to=2 73 | round_to = 0 if round_to < 0 else 15 if round_to > 15 else round_to # Ensure it's between 0 and 15 74 | bval = round(s_dict[biggest], round_to) 75 | # Split our number based on decimal points 76 | a,b = str(bval).split(".") 77 | # Check if we need to strip or pad zeroes 78 | b = b.rstrip("0") if strip_zeroes else b.ljust(round_to,"0") if round_to > 0 else "" 79 | return "{:,}{} {}".format(int(a),"" if not b else "."+b,biggest) 80 | 81 | def _progress_hook(self, bytes_so_far, total_size): 82 | if total_size > 0: 83 | percent = float(bytes_so_far) / total_size 84 | percent = round(percent*100, 2) 85 | t_s = self.get_size(total_size) 86 | try: b_s = self.get_size(bytes_so_far, t_s.split(" ")[1]) 87 | except: b_s = self.get_size(bytes_so_far) 88 | sys.stdout.write("\r\033[KDownloaded {} of {} ({:.2f}%)".format(b_s, t_s, percent)) 89 | else: 90 | b_s = self.get_size(bytes_so_far) 91 | sys.stdout.write("\r\033[KDownloaded {}".format(b_s)) 92 | 93 | def get_string(self, url, progress = True, headers = None, expand_gzip = True): 94 | response = self.get_bytes(url,progress,headers,expand_gzip) 95 | if response is None: return None 96 | return self._decode(response) 97 | 98 | def get_bytes(self, url, progress = True, headers = None, expand_gzip = True): 99 | response = self.open_url(url, headers) 100 | if response is None: return None 101 | bytes_so_far = 0 102 | try: total_size = int(response.headers['Content-Length']) 103 | except: total_size = -1 104 | chunk_so_far = b"" 105 | while True: 106 | chunk = response.read(self.chunk) 107 | bytes_so_far += len(chunk) 108 | if progress: self._progress_hook(bytes_so_far,total_size) 109 | if not chunk: break 110 | chunk_so_far += chunk 111 | if expand_gzip and response.headers.get("Content-Encoding","unknown").lower() == "gzip": 112 | fileobj = BytesIO(chunk_so_far) 113 | gfile = gzip.GzipFile(fileobj=fileobj) 114 | return gfile.read() 115 | if progress: print("") # Add a newline so our last progress prints completely 116 | return chunk_so_far 117 | 118 | def stream_to_file(self, url, file_path, progress = True, headers = None, ensure_size_if_present = True): 119 | response = self.open_url(url, headers) 120 | if response is None: return None 121 | bytes_so_far = 0 122 | try: total_size = int(response.headers['Content-Length']) 123 | except: total_size = -1 124 | with open(file_path, 'wb') as f: 125 | while True: 126 | chunk = response.read(self.chunk) 127 | bytes_so_far += len(chunk) 128 | if progress: self._progress_hook(bytes_so_far,total_size) 129 | if not chunk: break 130 | f.write(chunk) 131 | if progress: print("") # Add a newline so our last progress prints completely 132 | if ensure_size_if_present and total_size != -1: 133 | # We're verifying size - make sure we got what we asked for 134 | if bytes_so_far != total_size: 135 | return None # We didn't - imply it failed 136 | return file_path if os.path.exists(file_path) else None 137 | -------------------------------------------------------------------------------- /Scripts/plist.py: -------------------------------------------------------------------------------- 1 | ### ### 2 | # Imports # 3 | ### ### 4 | 5 | import datetime, os, plistlib, struct, sys, itertools, binascii 6 | from io import BytesIO 7 | 8 | if sys.version_info < (3,0): 9 | # Force use of StringIO instead of cStringIO as the latter 10 | # has issues with Unicode strings 11 | from StringIO import StringIO 12 | else: 13 | from io import StringIO 14 | 15 | try: 16 | basestring # Python 2 17 | unicode 18 | except NameError: 19 | basestring = str # Python 3 20 | unicode = str 21 | 22 | try: 23 | FMT_XML = plistlib.FMT_XML 24 | FMT_BINARY = plistlib.FMT_BINARY 25 | except AttributeError: 26 | FMT_XML = "FMT_XML" 27 | FMT_BINARY = "FMT_BINARY" 28 | 29 | ### ### 30 | # Helper Methods # 31 | ### ### 32 | 33 | def wrap_data(value): 34 | if not _check_py3(): return plistlib.Data(value) 35 | return value 36 | 37 | def extract_data(value): 38 | if not _check_py3() and isinstance(value,plistlib.Data): return value.data 39 | return value 40 | 41 | def _check_py3(): 42 | return sys.version_info >= (3, 0) 43 | 44 | def _is_binary(fp): 45 | if isinstance(fp, basestring): 46 | return fp.startswith(b"bplist00") 47 | header = fp.read(32) 48 | fp.seek(0) 49 | return header[:8] == b'bplist00' 50 | 51 | ### ### 52 | # Deprecated Functions - Remapped # 53 | ### ### 54 | 55 | def readPlist(pathOrFile): 56 | if not isinstance(pathOrFile, basestring): 57 | return load(pathOrFile) 58 | with open(pathOrFile, "rb") as f: 59 | return load(f) 60 | 61 | def writePlist(value, pathOrFile): 62 | if not isinstance(pathOrFile, basestring): 63 | return dump(value, pathOrFile, fmt=FMT_XML, sort_keys=True, skipkeys=False) 64 | with open(pathOrFile, "wb") as f: 65 | return dump(value, f, fmt=FMT_XML, sort_keys=True, skipkeys=False) 66 | 67 | ### ### 68 | # Remapped Functions # 69 | ### ### 70 | 71 | def load(fp, fmt=None, use_builtin_types=None, dict_type=dict): 72 | if _is_binary(fp): 73 | use_builtin_types = False if use_builtin_types is None else use_builtin_types 74 | try: 75 | p = _BinaryPlistParser(use_builtin_types=use_builtin_types, dict_type=dict_type) 76 | except: 77 | # Python 3.9 removed use_builtin_types 78 | p = _BinaryPlistParser(dict_type=dict_type) 79 | return p.parse(fp) 80 | elif _check_py3(): 81 | use_builtin_types = True if use_builtin_types is None else use_builtin_types 82 | # We need to monkey patch this to allow for hex integers - code taken/modified from 83 | # https://github.com/python/cpython/blob/3.8/Lib/plistlib.py 84 | if fmt is None: 85 | header = fp.read(32) 86 | fp.seek(0) 87 | for info in plistlib._FORMATS.values(): 88 | if info['detect'](header): 89 | P = info['parser'] 90 | break 91 | else: 92 | raise plistlib.InvalidFileException() 93 | else: 94 | P = plistlib._FORMATS[fmt]['parser'] 95 | try: 96 | p = P(use_builtin_types=use_builtin_types, dict_type=dict_type) 97 | except: 98 | # Python 3.9 removed use_builtin_types 99 | p = P(dict_type=dict_type) 100 | if isinstance(p,plistlib._PlistParser): 101 | # Monkey patch! 102 | def end_integer(): 103 | d = p.get_data() 104 | value = int(d,16) if d.lower().startswith("0x") else int(d) 105 | if -1 << 63 <= value < 1 << 64: 106 | p.add_object(value) 107 | else: 108 | raise OverflowError("Integer overflow at line {}".format(p.parser.CurrentLineNumber)) 109 | def end_data(): 110 | try: 111 | p.add_object(plistlib._decode_base64(p.get_data())) 112 | except Exception as e: 113 | raise Exception("Data error at line {}: {}".format(p.parser.CurrentLineNumber,e)) 114 | p.end_integer = end_integer 115 | p.end_data = end_data 116 | return p.parse(fp) 117 | else: 118 | # Is not binary - assume a string - and try to load 119 | # We avoid using readPlistFromString() as that uses 120 | # cStringIO and fails when Unicode strings are detected 121 | # Don't subclass - keep the parser local 122 | from xml.parsers.expat import ParserCreate 123 | # Create a new PlistParser object - then we need to set up 124 | # the values and parse. 125 | p = plistlib.PlistParser() 126 | parser = ParserCreate() 127 | parser.StartElementHandler = p.handleBeginElement 128 | parser.EndElementHandler = p.handleEndElement 129 | parser.CharacterDataHandler = p.handleData 130 | # We also need to monkey patch this to allow for other dict_types, hex int support 131 | # proper line output for data errors, and for unicode string decoding 132 | def begin_dict(attrs): 133 | d = dict_type() 134 | p.addObject(d) 135 | p.stack.append(d) 136 | def end_integer(): 137 | d = p.getData() 138 | value = int(d,16) if d.lower().startswith("0x") else int(d) 139 | if -1 << 63 <= value < 1 << 64: 140 | p.addObject(value) 141 | else: 142 | raise OverflowError("Integer overflow at line {}".format(parser.CurrentLineNumber)) 143 | def end_data(): 144 | try: 145 | p.addObject(plistlib.Data.fromBase64(p.getData())) 146 | except Exception as e: 147 | raise Exception("Data error at line {}: {}".format(parser.CurrentLineNumber,e)) 148 | def end_string(): 149 | d = p.getData() 150 | if isinstance(d,unicode): 151 | d = d.encode("utf-8") 152 | p.addObject(d) 153 | p.begin_dict = begin_dict 154 | p.end_integer = end_integer 155 | p.end_data = end_data 156 | p.end_string = end_string 157 | if isinstance(fp, unicode): 158 | # Encode unicode -> string; use utf-8 for safety 159 | fp = fp.encode("utf-8") 160 | if isinstance(fp, basestring): 161 | # It's a string - let's wrap it up 162 | fp = StringIO(fp) 163 | # Parse it 164 | parser.ParseFile(fp) 165 | return p.root 166 | 167 | def loads(value, fmt=None, use_builtin_types=None, dict_type=dict): 168 | if _check_py3() and isinstance(value, basestring): 169 | # If it's a string - encode it 170 | value = value.encode() 171 | try: 172 | return load(BytesIO(value),fmt=fmt,use_builtin_types=use_builtin_types,dict_type=dict_type) 173 | except: 174 | # Python 3.9 removed use_builtin_types 175 | return load(BytesIO(value),fmt=fmt,dict_type=dict_type) 176 | 177 | def dump(value, fp, fmt=FMT_XML, sort_keys=True, skipkeys=False): 178 | if fmt == FMT_BINARY: 179 | # Assume binary at this point 180 | writer = _BinaryPlistWriter(fp, sort_keys=sort_keys, skipkeys=skipkeys) 181 | writer.write(value) 182 | elif fmt == FMT_XML: 183 | if _check_py3(): 184 | plistlib.dump(value, fp, fmt=fmt, sort_keys=sort_keys, skipkeys=skipkeys) 185 | else: 186 | # We need to monkey patch a bunch here too in order to avoid auto-sorting 187 | # of keys 188 | writer = plistlib.PlistWriter(fp) 189 | def writeDict(d): 190 | if d: 191 | writer.beginElement("dict") 192 | items = sorted(d.items()) if sort_keys else d.items() 193 | for key, value in items: 194 | if not isinstance(key, basestring): 195 | if skipkeys: 196 | continue 197 | raise TypeError("keys must be strings") 198 | writer.simpleElement("key", key) 199 | writer.writeValue(value) 200 | writer.endElement("dict") 201 | else: 202 | writer.simpleElement("dict") 203 | writer.writeDict = writeDict 204 | writer.writeln("") 205 | writer.writeValue(value) 206 | writer.writeln("") 207 | else: 208 | # Not a proper format 209 | raise ValueError("Unsupported format: {}".format(fmt)) 210 | 211 | def dumps(value, fmt=FMT_XML, skipkeys=False, sort_keys=True): 212 | # We avoid using writePlistToString() as that uses 213 | # cStringIO and fails when Unicode strings are detected 214 | f = BytesIO() if _check_py3() else StringIO() 215 | dump(value, f, fmt=fmt, skipkeys=skipkeys, sort_keys=sort_keys) 216 | value = f.getvalue() 217 | if _check_py3(): 218 | value = value.decode("utf-8") 219 | return value 220 | 221 | ### ### 222 | # Binary Plist Stuff For Py2 # 223 | ### ### 224 | 225 | # From the python 3 plistlib.py source: https://github.com/python/cpython/blob/3.11/Lib/plistlib.py 226 | # Tweaked to function on both Python 2 and 3 227 | 228 | class UID: 229 | def __init__(self, data): 230 | if not isinstance(data, int): 231 | raise TypeError("data must be an int") 232 | # It seems Apple only uses 32-bit unsigned ints for UIDs. Although the comment in 233 | # CoreFoundation's CFBinaryPList.c detailing the binary plist format theoretically 234 | # allows for 64-bit UIDs, most functions in the same file use 32-bit unsigned ints, 235 | # with the sole function hinting at 64-bits appearing to be a leftover from copying 236 | # and pasting integer handling code internally, and this code has not changed since 237 | # it was added. (In addition, code in CFPropertyList.c to handle CF$UID also uses a 238 | # 32-bit unsigned int.) 239 | # 240 | # if data >= 1 << 64: 241 | # raise ValueError("UIDs cannot be >= 2**64") 242 | if data >= 1 << 32: 243 | raise ValueError("UIDs cannot be >= 2**32 (4294967296)") 244 | if data < 0: 245 | raise ValueError("UIDs must be positive") 246 | self.data = data 247 | 248 | def __index__(self): 249 | return self.data 250 | 251 | def __repr__(self): 252 | return "%s(%s)" % (self.__class__.__name__, repr(self.data)) 253 | 254 | def __reduce__(self): 255 | return self.__class__, (self.data,) 256 | 257 | def __eq__(self, other): 258 | if not isinstance(other, UID): 259 | return NotImplemented 260 | return self.data == other.data 261 | 262 | def __hash__(self): 263 | return hash(self.data) 264 | 265 | class InvalidFileException (ValueError): 266 | def __init__(self, message="Invalid file"): 267 | ValueError.__init__(self, message) 268 | 269 | _BINARY_FORMAT = {1: 'B', 2: 'H', 4: 'L', 8: 'Q'} 270 | 271 | _undefined = object() 272 | 273 | class _BinaryPlistParser: 274 | """ 275 | Read or write a binary plist file, following the description of the binary 276 | format. Raise InvalidFileException in case of error, otherwise return the 277 | root object. 278 | see also: http://opensource.apple.com/source/CF/CF-744.18/CFBinaryPList.c 279 | """ 280 | def __init__(self, use_builtin_types, dict_type): 281 | self._use_builtin_types = use_builtin_types 282 | self._dict_type = dict_type 283 | 284 | def parse(self, fp): 285 | try: 286 | # The basic file format: 287 | # HEADER 288 | # object... 289 | # refid->offset... 290 | # TRAILER 291 | self._fp = fp 292 | self._fp.seek(-32, os.SEEK_END) 293 | trailer = self._fp.read(32) 294 | if len(trailer) != 32: 295 | raise InvalidFileException() 296 | ( 297 | offset_size, self._ref_size, num_objects, top_object, 298 | offset_table_offset 299 | ) = struct.unpack('>6xBBQQQ', trailer) 300 | self._fp.seek(offset_table_offset) 301 | self._object_offsets = self._read_ints(num_objects, offset_size) 302 | self._objects = [_undefined] * num_objects 303 | return self._read_object(top_object) 304 | 305 | except (OSError, IndexError, struct.error, OverflowError, 306 | UnicodeDecodeError): 307 | raise InvalidFileException() 308 | 309 | def _get_size(self, tokenL): 310 | """ return the size of the next object.""" 311 | if tokenL == 0xF: 312 | m = self._fp.read(1)[0] 313 | if not _check_py3(): 314 | m = ord(m) 315 | m = m & 0x3 316 | s = 1 << m 317 | f = '>' + _BINARY_FORMAT[s] 318 | return struct.unpack(f, self._fp.read(s))[0] 319 | 320 | return tokenL 321 | 322 | def _read_ints(self, n, size): 323 | data = self._fp.read(size * n) 324 | if size in _BINARY_FORMAT: 325 | return struct.unpack('>' + _BINARY_FORMAT[size] * n, data) 326 | else: 327 | if not size or len(data) != size * n: 328 | raise InvalidFileException() 329 | return tuple(int(binascii.hexlify(data[i: i + size]),16) 330 | for i in range(0, size * n, size)) 331 | '''return tuple(int.from_bytes(data[i: i + size], 'big') 332 | for i in range(0, size * n, size))''' 333 | 334 | def _read_refs(self, n): 335 | return self._read_ints(n, self._ref_size) 336 | 337 | def _read_object(self, ref): 338 | """ 339 | read the object by reference. 340 | May recursively read sub-objects (content of an array/dict/set) 341 | """ 342 | result = self._objects[ref] 343 | if result is not _undefined: 344 | return result 345 | 346 | offset = self._object_offsets[ref] 347 | self._fp.seek(offset) 348 | token = self._fp.read(1)[0] 349 | if not _check_py3(): 350 | token = ord(token) 351 | tokenH, tokenL = token & 0xF0, token & 0x0F 352 | 353 | if token == 0x00: # \x00 or 0x00 354 | result = None 355 | 356 | elif token == 0x08: # \x08 or 0x08 357 | result = False 358 | 359 | elif token == 0x09: # \x09 or 0x09 360 | result = True 361 | 362 | # The referenced source code also mentions URL (0x0c, 0x0d) and 363 | # UUID (0x0e), but neither can be generated using the Cocoa libraries. 364 | 365 | elif token == 0x0f: # \x0f or 0x0f 366 | result = b'' 367 | 368 | elif tokenH == 0x10: # int 369 | result = int(binascii.hexlify(self._fp.read(1 << tokenL)),16) 370 | if tokenL >= 3: # Signed - adjust 371 | result = result-((result & 0x8000000000000000) << 1) 372 | 373 | elif token == 0x22: # real 374 | result = struct.unpack('>f', self._fp.read(4))[0] 375 | 376 | elif token == 0x23: # real 377 | result = struct.unpack('>d', self._fp.read(8))[0] 378 | 379 | elif token == 0x33: # date 380 | f = struct.unpack('>d', self._fp.read(8))[0] 381 | # timestamp 0 of binary plists corresponds to 1/1/2001 382 | # (year of Mac OS X 10.0), instead of 1/1/1970. 383 | result = (datetime.datetime(2001, 1, 1) + 384 | datetime.timedelta(seconds=f)) 385 | 386 | elif tokenH == 0x40: # data 387 | s = self._get_size(tokenL) 388 | if self._use_builtin_types or not hasattr(plistlib, "Data"): 389 | result = self._fp.read(s) 390 | else: 391 | result = plistlib.Data(self._fp.read(s)) 392 | 393 | elif tokenH == 0x50: # ascii string 394 | s = self._get_size(tokenL) 395 | result = self._fp.read(s).decode('ascii') 396 | result = result 397 | 398 | elif tokenH == 0x60: # unicode string 399 | s = self._get_size(tokenL) 400 | result = self._fp.read(s * 2).decode('utf-16be') 401 | 402 | elif tokenH == 0x80: # UID 403 | # used by Key-Archiver plist files 404 | result = UID(int(binascii.hexlify(self._fp.read(1 + tokenL)),16)) 405 | 406 | elif tokenH == 0xA0: # array 407 | s = self._get_size(tokenL) 408 | obj_refs = self._read_refs(s) 409 | result = [] 410 | self._objects[ref] = result 411 | result.extend(self._read_object(x) for x in obj_refs) 412 | 413 | # tokenH == 0xB0 is documented as 'ordset', but is not actually 414 | # implemented in the Apple reference code. 415 | 416 | # tokenH == 0xC0 is documented as 'set', but sets cannot be used in 417 | # plists. 418 | 419 | elif tokenH == 0xD0: # dict 420 | s = self._get_size(tokenL) 421 | key_refs = self._read_refs(s) 422 | obj_refs = self._read_refs(s) 423 | result = self._dict_type() 424 | self._objects[ref] = result 425 | for k, o in zip(key_refs, obj_refs): 426 | key = self._read_object(k) 427 | if hasattr(plistlib, "Data") and isinstance(key, plistlib.Data): 428 | key = key.data 429 | result[key] = self._read_object(o) 430 | 431 | else: 432 | raise InvalidFileException() 433 | 434 | self._objects[ref] = result 435 | return result 436 | 437 | def _count_to_size(count): 438 | if count < 1 << 8: 439 | return 1 440 | 441 | elif count < 1 << 16: 442 | return 2 443 | 444 | elif count < 1 << 32: 445 | return 4 446 | 447 | else: 448 | return 8 449 | 450 | _scalars = (str, int, float, datetime.datetime, bytes) 451 | 452 | class _BinaryPlistWriter (object): 453 | def __init__(self, fp, sort_keys, skipkeys): 454 | self._fp = fp 455 | self._sort_keys = sort_keys 456 | self._skipkeys = skipkeys 457 | 458 | def write(self, value): 459 | 460 | # Flattened object list: 461 | self._objlist = [] 462 | 463 | # Mappings from object->objectid 464 | # First dict has (type(object), object) as the key, 465 | # second dict is used when object is not hashable and 466 | # has id(object) as the key. 467 | self._objtable = {} 468 | self._objidtable = {} 469 | 470 | # Create list of all objects in the plist 471 | self._flatten(value) 472 | 473 | # Size of object references in serialized containers 474 | # depends on the number of objects in the plist. 475 | num_objects = len(self._objlist) 476 | self._object_offsets = [0]*num_objects 477 | self._ref_size = _count_to_size(num_objects) 478 | 479 | self._ref_format = _BINARY_FORMAT[self._ref_size] 480 | 481 | # Write file header 482 | self._fp.write(b'bplist00') 483 | 484 | # Write object list 485 | for obj in self._objlist: 486 | self._write_object(obj) 487 | 488 | # Write refnum->object offset table 489 | top_object = self._getrefnum(value) 490 | offset_table_offset = self._fp.tell() 491 | offset_size = _count_to_size(offset_table_offset) 492 | offset_format = '>' + _BINARY_FORMAT[offset_size] * num_objects 493 | self._fp.write(struct.pack(offset_format, *self._object_offsets)) 494 | 495 | # Write trailer 496 | sort_version = 0 497 | trailer = ( 498 | sort_version, offset_size, self._ref_size, num_objects, 499 | top_object, offset_table_offset 500 | ) 501 | self._fp.write(struct.pack('>5xBBBQQQ', *trailer)) 502 | 503 | def _flatten(self, value): 504 | # First check if the object is in the object table, not used for 505 | # containers to ensure that two subcontainers with the same contents 506 | # will be serialized as distinct values. 507 | if isinstance(value, _scalars): 508 | if (type(value), value) in self._objtable: 509 | return 510 | 511 | elif hasattr(plistlib, "Data") and isinstance(value, plistlib.Data): 512 | if (type(value.data), value.data) in self._objtable: 513 | return 514 | 515 | elif id(value) in self._objidtable: 516 | return 517 | 518 | # Add to objectreference map 519 | refnum = len(self._objlist) 520 | self._objlist.append(value) 521 | if isinstance(value, _scalars): 522 | self._objtable[(type(value), value)] = refnum 523 | elif hasattr(plistlib, "Data") and isinstance(value, plistlib.Data): 524 | self._objtable[(type(value.data), value.data)] = refnum 525 | else: 526 | self._objidtable[id(value)] = refnum 527 | 528 | # And finally recurse into containers 529 | if isinstance(value, dict): 530 | keys = [] 531 | values = [] 532 | items = value.items() 533 | if self._sort_keys: 534 | items = sorted(items) 535 | 536 | for k, v in items: 537 | if not isinstance(k, basestring): 538 | if self._skipkeys: 539 | continue 540 | raise TypeError("keys must be strings") 541 | keys.append(k) 542 | values.append(v) 543 | 544 | for o in itertools.chain(keys, values): 545 | self._flatten(o) 546 | 547 | elif isinstance(value, (list, tuple)): 548 | for o in value: 549 | self._flatten(o) 550 | 551 | def _getrefnum(self, value): 552 | if isinstance(value, _scalars): 553 | return self._objtable[(type(value), value)] 554 | elif hasattr(plistlib, "Data") and isinstance(value, plistlib.Data): 555 | return self._objtable[(type(value.data), value.data)] 556 | else: 557 | return self._objidtable[id(value)] 558 | 559 | def _write_size(self, token, size): 560 | if size < 15: 561 | self._fp.write(struct.pack('>B', token | size)) 562 | 563 | elif size < 1 << 8: 564 | self._fp.write(struct.pack('>BBB', token | 0xF, 0x10, size)) 565 | 566 | elif size < 1 << 16: 567 | self._fp.write(struct.pack('>BBH', token | 0xF, 0x11, size)) 568 | 569 | elif size < 1 << 32: 570 | self._fp.write(struct.pack('>BBL', token | 0xF, 0x12, size)) 571 | 572 | else: 573 | self._fp.write(struct.pack('>BBQ', token | 0xF, 0x13, size)) 574 | 575 | def _write_object(self, value): 576 | ref = self._getrefnum(value) 577 | self._object_offsets[ref] = self._fp.tell() 578 | if value is None: 579 | self._fp.write(b'\x00') 580 | 581 | elif value is False: 582 | self._fp.write(b'\x08') 583 | 584 | elif value is True: 585 | self._fp.write(b'\x09') 586 | 587 | elif isinstance(value, int): 588 | if value < 0: 589 | try: 590 | self._fp.write(struct.pack('>Bq', 0x13, value)) 591 | except struct.error: 592 | raise OverflowError(value) # from None 593 | elif value < 1 << 8: 594 | self._fp.write(struct.pack('>BB', 0x10, value)) 595 | elif value < 1 << 16: 596 | self._fp.write(struct.pack('>BH', 0x11, value)) 597 | elif value < 1 << 32: 598 | self._fp.write(struct.pack('>BL', 0x12, value)) 599 | elif value < 1 << 63: 600 | self._fp.write(struct.pack('>BQ', 0x13, value)) 601 | elif value < 1 << 64: 602 | self._fp.write(b'\x14' + value.to_bytes(16, 'big', signed=True)) 603 | else: 604 | raise OverflowError(value) 605 | 606 | elif isinstance(value, float): 607 | self._fp.write(struct.pack('>Bd', 0x23, value)) 608 | 609 | elif isinstance(value, datetime.datetime): 610 | f = (value - datetime.datetime(2001, 1, 1)).total_seconds() 611 | self._fp.write(struct.pack('>Bd', 0x33, f)) 612 | 613 | elif (_check_py3() and isinstance(value, (bytes, bytearray))) or (hasattr(plistlib, "Data") and isinstance(value, plistlib.Data)): 614 | if not isinstance(value, (bytes, bytearray)): 615 | value = value.data # Unpack it 616 | self._write_size(0x40, len(value)) 617 | self._fp.write(value) 618 | 619 | elif isinstance(value, basestring): 620 | try: 621 | t = value.encode('ascii') 622 | self._write_size(0x50, len(value)) 623 | except UnicodeEncodeError: 624 | t = value.encode('utf-16be') 625 | self._write_size(0x60, len(t) // 2) 626 | self._fp.write(t) 627 | 628 | elif isinstance(value, UID) or (hasattr(plistlib,"UID") and isinstance(value, plistlib.UID)): 629 | if value.data < 0: 630 | raise ValueError("UIDs must be positive") 631 | elif value.data < 1 << 8: 632 | self._fp.write(struct.pack('>BB', 0x80, value)) 633 | elif value.data < 1 << 16: 634 | self._fp.write(struct.pack('>BH', 0x81, value)) 635 | elif value.data < 1 << 32: 636 | self._fp.write(struct.pack('>BL', 0x83, value)) 637 | # elif value.data < 1 << 64: 638 | # self._fp.write(struct.pack('>BQ', 0x87, value)) 639 | else: 640 | raise OverflowError(value) 641 | 642 | elif isinstance(value, (list, tuple)): 643 | refs = [self._getrefnum(o) for o in value] 644 | s = len(refs) 645 | self._write_size(0xA0, s) 646 | self._fp.write(struct.pack('>' + self._ref_format * s, *refs)) 647 | 648 | elif isinstance(value, dict): 649 | keyRefs, valRefs = [], [] 650 | 651 | if self._sort_keys: 652 | rootItems = sorted(value.items()) 653 | else: 654 | rootItems = value.items() 655 | 656 | for k, v in rootItems: 657 | if not isinstance(k, basestring): 658 | if self._skipkeys: 659 | continue 660 | raise TypeError("keys must be strings") 661 | keyRefs.append(self._getrefnum(k)) 662 | valRefs.append(self._getrefnum(v)) 663 | 664 | s = len(keyRefs) 665 | self._write_size(0xD0, s) 666 | self._fp.write(struct.pack('>' + self._ref_format * s, *keyRefs)) 667 | self._fp.write(struct.pack('>' + self._ref_format * s, *valRefs)) 668 | 669 | else: 670 | raise TypeError(value) 671 | -------------------------------------------------------------------------------- /Scripts/run.py: -------------------------------------------------------------------------------- 1 | import sys, subprocess, time, threading, shlex 2 | try: 3 | from Queue import Queue, Empty 4 | except: 5 | from queue import Queue, Empty 6 | 7 | ON_POSIX = 'posix' in sys.builtin_module_names 8 | 9 | class Run: 10 | 11 | def __init__(self): 12 | return 13 | 14 | def _read_output(self, pipe, q): 15 | try: 16 | for line in iter(lambda: pipe.read(1), b''): 17 | q.put(line) 18 | except ValueError: 19 | pass 20 | pipe.close() 21 | 22 | def _create_thread(self, output): 23 | # Creates a new queue and thread object to watch based on the output pipe sent 24 | q = Queue() 25 | t = threading.Thread(target=self._read_output, args=(output, q)) 26 | t.daemon = True 27 | return (q,t) 28 | 29 | def _stream_output(self, comm, shell = False): 30 | output = error = "" 31 | p = None 32 | try: 33 | if shell and type(comm) is list: 34 | comm = " ".join(shlex.quote(x) for x in comm) 35 | if not shell and type(comm) is str: 36 | comm = shlex.split(comm) 37 | p = subprocess.Popen(comm, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=0, universal_newlines=True, close_fds=ON_POSIX) 38 | # Setup the stdout thread/queue 39 | q,t = self._create_thread(p.stdout) 40 | qe,te = self._create_thread(p.stderr) 41 | # Start both threads 42 | t.start() 43 | te.start() 44 | 45 | while True: 46 | c = z = "" 47 | try: c = q.get_nowait() 48 | except Empty: pass 49 | else: 50 | sys.stdout.write(c) 51 | output += c 52 | sys.stdout.flush() 53 | try: z = qe.get_nowait() 54 | except Empty: pass 55 | else: 56 | sys.stderr.write(z) 57 | error += z 58 | sys.stderr.flush() 59 | if not c==z=="": continue # Keep going until empty 60 | # No output - see if still running 61 | p.poll() 62 | if p.returncode != None: 63 | # Subprocess ended 64 | break 65 | # No output, but subprocess still running - stall for 20ms 66 | time.sleep(0.02) 67 | 68 | o, e = p.communicate() 69 | return (output+o, error+e, p.returncode) 70 | except: 71 | if p: 72 | try: o, e = p.communicate() 73 | except: o = e = "" 74 | return (output+o, error+e, p.returncode) 75 | return ("", "Command not found!", 1) 76 | 77 | def _decode(self, value, encoding="utf-8", errors="ignore"): 78 | # Helper method to only decode if bytes type 79 | if sys.version_info >= (3,0) and isinstance(value, bytes): 80 | return value.decode(encoding,errors) 81 | return value 82 | 83 | def _run_command(self, comm, shell = False): 84 | c = None 85 | try: 86 | if shell and type(comm) is list: 87 | comm = " ".join(shlex.quote(x) for x in comm) 88 | if not shell and type(comm) is str: 89 | comm = shlex.split(comm) 90 | p = subprocess.Popen(comm, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 91 | c = p.communicate() 92 | except: 93 | if c == None: 94 | return ("", "Command not found!", 1) 95 | return (self._decode(c[0]), self._decode(c[1]), p.returncode) 96 | 97 | def run(self, command_list, leave_on_fail = False): 98 | # Command list should be an array of dicts 99 | if type(command_list) is dict: 100 | # We only have one command 101 | command_list = [command_list] 102 | output_list = [] 103 | for comm in command_list: 104 | args = comm.get("args", []) 105 | shell = comm.get("shell", False) 106 | stream = comm.get("stream", False) 107 | sudo = comm.get("sudo", False) 108 | stdout = comm.get("stdout", False) 109 | stderr = comm.get("stderr", False) 110 | mess = comm.get("message", None) 111 | show = comm.get("show", False) 112 | 113 | if not mess == None: 114 | print(mess) 115 | 116 | if not len(args): 117 | # nothing to process 118 | continue 119 | if sudo: 120 | # Check if we have sudo 121 | out = self._run_command(["which", "sudo"]) 122 | if "sudo" in out[0]: 123 | # Can sudo 124 | if type(args) is list: 125 | args.insert(0, out[0].replace("\n", "")) # add to start of list 126 | elif type(args) is str: 127 | args = out[0].replace("\n", "") + " " + args # add to start of string 128 | 129 | if show: 130 | print(" ".join(args)) 131 | 132 | if stream: 133 | # Stream it! 134 | out = self._stream_output(args, shell) 135 | else: 136 | # Just run and gather output 137 | out = self._run_command(args, shell) 138 | if stdout and len(out[0]): 139 | print(out[0]) 140 | if stderr and len(out[1]): 141 | print(out[1]) 142 | # Append output 143 | output_list.append(out) 144 | # Check for errors 145 | if leave_on_fail and out[2] != 0: 146 | # Got an error - leave 147 | break 148 | if len(output_list) == 1: 149 | # We only ran one command - just return that output 150 | return output_list[0] 151 | return output_list 152 | -------------------------------------------------------------------------------- /Scripts/utils.py: -------------------------------------------------------------------------------- 1 | import sys, os, time, re, json, datetime, ctypes, subprocess 2 | 3 | if os.name == "nt": 4 | # Windows 5 | import msvcrt 6 | else: 7 | # Not Windows \o/ 8 | import select 9 | 10 | class Utils: 11 | 12 | def __init__(self, name = "Python Script"): 13 | self.name = name 14 | # Init our colors before we need to print anything 15 | cwd = os.getcwd() 16 | os.chdir(os.path.dirname(os.path.realpath(__file__))) 17 | if os.path.exists("colors.json"): 18 | self.colors_dict = json.load(open("colors.json")) 19 | else: 20 | self.colors_dict = {} 21 | os.chdir(cwd) 22 | 23 | def check_admin(self): 24 | # Returns whether or not we're admin 25 | try: 26 | is_admin = os.getuid() == 0 27 | except AttributeError: 28 | is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0 29 | return is_admin 30 | 31 | def elevate(self, file): 32 | # Runs the passed file as admin 33 | if self.check_admin(): 34 | return 35 | if os.name == "nt": 36 | ctypes.windll.shell32.ShellExecuteW(None, "runas", '"{}"'.format(sys.executable), '"{}"'.format(file), None, 1) 37 | else: 38 | try: 39 | p = subprocess.Popen(["which", "sudo"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) 40 | c = p.communicate()[0].decode("utf-8", "ignore").replace("\n", "") 41 | os.execv(c, [ sys.executable, 'python'] + sys.argv) 42 | except: 43 | exit(1) 44 | 45 | def compare_versions(self, vers1, vers2, **kwargs): 46 | # Helper method to compare ##.## strings 47 | # 48 | # vers1 < vers2 = True 49 | # vers1 = vers2 = None 50 | # vers1 > vers2 = False 51 | 52 | # Sanitize the pads 53 | pad = str(kwargs.get("pad", "")) 54 | sep = str(kwargs.get("separator", ".")) 55 | 56 | ignore_case = kwargs.get("ignore_case", True) 57 | 58 | # Cast as strings 59 | vers1 = str(vers1) 60 | vers2 = str(vers2) 61 | 62 | if ignore_case: 63 | vers1 = vers1.lower() 64 | vers2 = vers2.lower() 65 | 66 | # Split and pad lists 67 | v1_parts, v2_parts = self.pad_length(vers1.split(sep), vers2.split(sep)) 68 | 69 | # Iterate and compare 70 | for i in range(len(v1_parts)): 71 | # Remove non-numeric 72 | v1 = ''.join(c.lower() for c in v1_parts[i] if c.isalnum()) 73 | v2 = ''.join(c.lower() for c in v2_parts[i] if c.isalnum()) 74 | # Equalize the lengths 75 | v1, v2 = self.pad_length(v1, v2) 76 | # Compare 77 | if str(v1) < str(v2): 78 | return True 79 | elif str(v1) > str(v2): 80 | return False 81 | # Never differed - return None, must be equal 82 | return None 83 | 84 | def pad_length(self, var1, var2, pad = "0"): 85 | # Pads the vars on the left side to make them equal length 86 | pad = "0" if len(str(pad)) < 1 else str(pad)[0] 87 | if not type(var1) == type(var2): 88 | # Type mismatch! Just return what we got 89 | return (var1, var2) 90 | if len(var1) < len(var2): 91 | if type(var1) is list: 92 | var1.extend([str(pad) for x in range(len(var2) - len(var1))]) 93 | else: 94 | var1 = "{}{}".format((pad*(len(var2)-len(var1))), var1) 95 | elif len(var2) < len(var1): 96 | if type(var2) is list: 97 | var2.extend([str(pad) for x in range(len(var1) - len(var2))]) 98 | else: 99 | var2 = "{}{}".format((pad*(len(var1)-len(var2))), var2) 100 | return (var1, var2) 101 | 102 | def check_path(self, path): 103 | # Let's loop until we either get a working path, or no changes 104 | test_path = path 105 | last_path = None 106 | while True: 107 | # Bail if we've looped at least once and the path didn't change 108 | if last_path != None and last_path == test_path: return None 109 | last_path = test_path 110 | # Check if we stripped everything out 111 | if not len(test_path): return None 112 | # Check if we have a valid path 113 | if os.path.exists(test_path): 114 | return os.path.abspath(test_path) 115 | # Check for quotes 116 | if test_path[0] == test_path[-1] and test_path[0] in ('"',"'"): 117 | test_path = test_path[1:-1] 118 | continue 119 | # Check for a tilde and expand if needed 120 | if test_path[0] == "~": 121 | tilde_expanded = os.path.expanduser(test_path) 122 | if tilde_expanded != test_path: 123 | # Got a change 124 | test_path = tilde_expanded 125 | continue 126 | # Let's check for spaces - strip from the left first, then the right 127 | if test_path[0] in (" ","\t"): 128 | test_path = test_path[1:] 129 | continue 130 | if test_path[-1] in (" ","\t"): 131 | test_path = test_path[:-1] 132 | continue 133 | # Maybe we have escapes to handle? 134 | test_path = "\\".join([x.replace("\\", "") for x in test_path.split("\\\\")]) 135 | 136 | def grab(self, prompt, **kwargs): 137 | # Takes a prompt, a default, and a timeout and shows it with that timeout 138 | # returning the result 139 | timeout = kwargs.get("timeout", 0) 140 | default = kwargs.get("default", None) 141 | # If we don't have a timeout - then skip the timed sections 142 | if timeout <= 0: 143 | if sys.version_info >= (3, 0): 144 | return input(prompt) 145 | else: 146 | return str(raw_input(prompt)) 147 | # Write our prompt 148 | sys.stdout.write(prompt) 149 | sys.stdout.flush() 150 | if os.name == "nt": 151 | start_time = time.time() 152 | i = '' 153 | while True: 154 | if msvcrt.kbhit(): 155 | c = msvcrt.getche() 156 | if ord(c) == 13: # enter_key 157 | break 158 | elif ord(c) >= 32: #space_char 159 | i += c 160 | if len(i) == 0 and (time.time() - start_time) > timeout: 161 | break 162 | else: 163 | i, o, e = select.select( [sys.stdin], [], [], timeout ) 164 | if i: 165 | i = sys.stdin.readline().strip() 166 | print('') # needed to move to next line 167 | if len(i) > 0: 168 | return i 169 | else: 170 | return default 171 | 172 | def cls(self): 173 | os.system('cls' if os.name=='nt' else 'clear') 174 | 175 | def cprint(self, message, **kwargs): 176 | strip_colors = kwargs.get("strip_colors", False) 177 | if os.name == "nt": 178 | strip_colors = True 179 | reset = u"\u001b[0m" 180 | # Requires sys import 181 | for c in self.colors: 182 | if strip_colors: 183 | message = message.replace(c["find"], "") 184 | else: 185 | message = message.replace(c["find"], c["replace"]) 186 | if strip_colors: 187 | return message 188 | sys.stdout.write(message) 189 | print(reset) 190 | 191 | # Needs work to resize the string if color chars exist 192 | '''# Header drawing method 193 | def head(self, text = None, width = 55): 194 | if text == None: 195 | text = self.name 196 | self.cls() 197 | print(" {}".format("#"*width)) 198 | len_text = self.cprint(text, strip_colors=True) 199 | mid_len = int(round(width/2-len(len_text)/2)-2) 200 | middle = " #{}{}{}#".format(" "*mid_len, len_text, " "*((width - mid_len - len(len_text))-2)) 201 | if len(middle) > width+1: 202 | # Get the difference 203 | di = len(middle) - width 204 | # Add the padding for the ...# 205 | di += 3 206 | # Trim the string 207 | middle = middle[:-di] 208 | newlen = len(middle) 209 | middle += "...#" 210 | find_list = [ c["find"] for c in self.colors ] 211 | 212 | # Translate colored string to len 213 | middle = middle.replace(len_text, text + self.rt_color) # always reset just in case 214 | self.cprint(middle) 215 | print("#"*width)''' 216 | 217 | # Header drawing method 218 | def head(self, text = None, width = 55): 219 | if text == None: 220 | text = self.name 221 | self.cls() 222 | print(" {}".format("#"*width)) 223 | mid_len = int(round(width/2-len(text)/2)-2) 224 | middle = " #{}{}{}#".format(" "*mid_len, text, " "*((width - mid_len - len(text))-2)) 225 | if len(middle) > width+1: 226 | # Get the difference 227 | di = len(middle) - width 228 | # Add the padding for the ...# 229 | di += 3 230 | # Trim the string 231 | middle = middle[:-di] + "...#" 232 | print(middle) 233 | print("#"*width) 234 | 235 | def resize(self, width, height): 236 | print('\033[8;{};{}t'.format(height, width)) 237 | 238 | def custom_quit(self): 239 | self.head() 240 | print("by CorpNewt\n") 241 | print("Thanks for testing it out, for bugs/comments/complaints") 242 | print("send me a message on Reddit, or check out my GitHub:\n") 243 | print("www.reddit.com/u/corpnewt") 244 | print("www.github.com/corpnewt\n") 245 | # Get the time and wish them a good morning, afternoon, evening, and night 246 | hr = datetime.datetime.now().time().hour 247 | if hr > 3 and hr < 12: 248 | print("Have a nice morning!\n\n") 249 | elif hr >= 12 and hr < 17: 250 | print("Have a nice afternoon!\n\n") 251 | elif hr >= 17 and hr < 21: 252 | print("Have a nice evening!\n\n") 253 | else: 254 | print("Have a nice night!\n\n") 255 | exit(0) 256 | --------------------------------------------------------------------------------