├── .gitattributes ├── .gitignore ├── GenSMBIOS.bat ├── GenSMBIOS.command ├── GenSMBIOS.py ├── LICENSE ├── README.md └── Scripts ├── __init__.py ├── downloader.py ├── plist.py ├── prefix.json ├── run.py └── utils.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Ensure all .bat scripts use CRLF line endings 2 | # This can prevent a number of odd batch issues 3 | *.bat text eol=crlf 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # macserial anything 7 | macserial* 8 | 9 | # Hidden files 10 | .* 11 | 12 | # C extensions 13 | *.so 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 | -------------------------------------------------------------------------------- /GenSMBIOS.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM Get our local path before delayed expansion - allows ! in path 3 | set "thisDir=%~dp0" 4 | 5 | setlocal enableDelayedExpansion 6 | REM Setup initial vars 7 | set "script_name=" 8 | set /a tried=0 9 | set "toask=yes" 10 | set "pause_on_error=yes" 11 | set "py2v=" 12 | set "py2path=" 13 | set "py3v=" 14 | set "py3path=" 15 | set "pypath=" 16 | set "targetpy=3" 17 | 18 | REM use_py3: 19 | REM TRUE = Use if found, use py2 otherwise 20 | REM FALSE = Use py2 21 | REM FORCE = Use py3 22 | set "use_py3=TRUE" 23 | 24 | REM We'll parse if the first argument passed is 25 | REM --install-python and if so, we'll just install 26 | set "just_installing=FALSE" 27 | 28 | REM Get the system32 (or equivalent) path 29 | call :getsyspath "syspath" 30 | 31 | REM Make sure the syspath exists 32 | if "!syspath!" == "" ( 33 | if exist "%SYSTEMROOT%\system32\cmd.exe" ( 34 | if exist "%SYSTEMROOT%\system32\reg.exe" ( 35 | if exist "%SYSTEMROOT%\system32\where.exe" ( 36 | REM Fall back on the default path if it exists 37 | set "ComSpec=%SYSTEMROOT%\system32\cmd.exe" 38 | set "syspath=%SYSTEMROOT%\system32\" 39 | ) 40 | ) 41 | ) 42 | if "!syspath!" == "" ( 43 | cls 44 | echo ### ### 45 | echo # Warning # 46 | echo ### ### 47 | echo. 48 | echo Could not locate cmd.exe, reg.exe, or where.exe 49 | echo. 50 | echo Please ensure your ComSpec environment variable is properly configured and 51 | echo points directly to cmd.exe, then try again. 52 | echo. 53 | echo Current CompSpec Value: "%ComSpec%" 54 | echo. 55 | echo Press [enter] to quit. 56 | pause > nul 57 | exit /b 1 58 | ) 59 | ) 60 | 61 | if "%~1" == "--install-python" ( 62 | set "just_installing=TRUE" 63 | goto installpy 64 | ) 65 | 66 | goto checkscript 67 | 68 | :checkscript 69 | REM Check for our script first 70 | set "looking_for=!script_name!" 71 | if "!script_name!" == "" ( 72 | set "looking_for=%~n0.py or %~n0.command" 73 | set "script_name=%~n0.py" 74 | if not exist "!thisDir!\!script_name!" ( 75 | set "script_name=%~n0.command" 76 | ) 77 | ) 78 | if not exist "!thisDir!\!script_name!" ( 79 | echo Could not find !looking_for!. 80 | echo Please make sure to run this script from the same directory 81 | echo as !looking_for!. 82 | echo. 83 | echo Press [enter] to quit. 84 | pause > nul 85 | exit /b 1 86 | ) 87 | goto checkpy 88 | 89 | :checkpy 90 | call :updatepath 91 | for /f "USEBACKQ tokens=*" %%x in (`!syspath!where.exe python 2^> nul`) do ( call :checkpyversion "%%x" "py2v" "py2path" "py3v" "py3path" ) 92 | for /f "USEBACKQ tokens=*" %%x in (`!syspath!where.exe python3 2^> nul`) do ( call :checkpyversion "%%x" "py2v" "py2path" "py3v" "py3path" ) 93 | for /f "USEBACKQ tokens=*" %%x in (`!syspath!where.exe py 2^> nul`) do ( call :checkpylauncher "%%x" "py2v" "py2path" "py3v" "py3path" ) 94 | REM Walk our returns to see if we need to install 95 | if /i "!use_py3!" == "FALSE" ( 96 | set "targetpy=2" 97 | set "pypath=!py2path!" 98 | ) else if /i "!use_py3!" == "FORCE" ( 99 | set "pypath=!py3path!" 100 | ) else if /i "!use_py3!" == "TRUE" ( 101 | set "pypath=!py3path!" 102 | if "!pypath!" == "" set "pypath=!py2path!" 103 | ) 104 | if not "!pypath!" == "" ( 105 | goto runscript 106 | ) 107 | if !tried! lss 1 ( 108 | if /i "!toask!"=="yes" ( 109 | REM Better ask permission first 110 | goto askinstall 111 | ) else ( 112 | goto installpy 113 | ) 114 | ) else ( 115 | cls 116 | echo ### ### 117 | echo # Warning # 118 | echo ### ### 119 | echo. 120 | REM Couldn't install for whatever reason - give the error message 121 | echo Python is not installed or not found in your PATH var. 122 | echo Please install it from https://www.python.org/downloads/windows/ 123 | echo. 124 | echo Make sure you check the box labeled: 125 | echo. 126 | echo "Add Python X.X to PATH" 127 | echo. 128 | echo Where X.X is the py version you're installing. 129 | echo. 130 | echo Press [enter] to quit. 131 | pause > nul 132 | exit /b 1 133 | ) 134 | goto runscript 135 | 136 | :checkpylauncher 137 | REM Attempt to check the latest python 2 and 3 versions via the py launcher 138 | for /f "USEBACKQ tokens=*" %%x in (`%~1 -2 -c "import sys; print(sys.executable)" 2^> nul`) do ( call :checkpyversion "%%x" "%~2" "%~3" "%~4" "%~5" ) 139 | for /f "USEBACKQ tokens=*" %%x in (`%~1 -3 -c "import sys; print(sys.executable)" 2^> nul`) do ( call :checkpyversion "%%x" "%~2" "%~3" "%~4" "%~5" ) 140 | goto :EOF 141 | 142 | :checkpyversion 143 | set "version="&for /f "tokens=2* USEBACKQ delims= " %%a in (`"%~1" -V 2^>^&1`) do ( 144 | REM Ensure we have a version number 145 | call :isnumber "%%a" 146 | if not "!errorlevel!" == "0" goto :EOF 147 | set "version=%%a" 148 | ) 149 | if not defined version goto :EOF 150 | if "!version:~0,1!" == "2" ( 151 | REM Python 2 152 | call :comparepyversion "!version!" "!%~2!" 153 | if "!errorlevel!" == "1" ( 154 | set "%~2=!version!" 155 | set "%~3=%~1" 156 | ) 157 | ) else ( 158 | REM Python 3 159 | call :comparepyversion "!version!" "!%~4!" 160 | if "!errorlevel!" == "1" ( 161 | set "%~4=!version!" 162 | set "%~5=%~1" 163 | ) 164 | ) 165 | goto :EOF 166 | 167 | :isnumber 168 | set "var="&for /f "delims=0123456789." %%i in ("%~1") do set var=%%i 169 | if defined var (exit /b 1) 170 | exit /b 0 171 | 172 | :comparepyversion 173 | REM Exits with status 0 if equal, 1 if v1 gtr v2, 2 if v1 lss v2 174 | for /f "tokens=1,2,3 delims=." %%a in ("%~1") do ( 175 | set a1=%%a 176 | set a2=%%b 177 | set a3=%%c 178 | ) 179 | for /f "tokens=1,2,3 delims=." %%a in ("%~2") do ( 180 | set b1=%%a 181 | set b2=%%b 182 | set b3=%%c 183 | ) 184 | if not defined a1 set a1=0 185 | if not defined a2 set a2=0 186 | if not defined a3 set a3=0 187 | if not defined b1 set b1=0 188 | if not defined b2 set b2=0 189 | if not defined b3 set b3=0 190 | if %a1% gtr %b1% exit /b 1 191 | if %a1% lss %b1% exit /b 2 192 | if %a2% gtr %b2% exit /b 1 193 | if %a2% lss %b2% exit /b 2 194 | if %a3% gtr %b3% exit /b 1 195 | if %a3% lss %b3% exit /b 2 196 | exit /b 0 197 | 198 | :askinstall 199 | cls 200 | echo ### ### 201 | echo # Python Not Found # 202 | echo ### ### 203 | echo. 204 | echo Python !targetpy! was not found on the system or in the PATH var. 205 | echo. 206 | set /p "menu=Would you like to install it now? [y/n]: " 207 | if /i "!menu!"=="y" ( 208 | REM We got the OK - install it 209 | goto installpy 210 | ) else if "!menu!"=="n" ( 211 | REM No OK here... 212 | set /a tried=!tried!+1 213 | goto checkpy 214 | ) 215 | REM Incorrect answer - go back 216 | goto askinstall 217 | 218 | :installpy 219 | REM This will attempt to download and install python 220 | REM First we get the html for the python downloads page for Windows 221 | set /a tried=!tried!+1 222 | cls 223 | echo ### ### 224 | echo # Installing Python # 225 | echo ### ### 226 | echo. 227 | echo Gathering info from https://www.python.org/downloads/windows/... 228 | powershell -command "[Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12;(new-object System.Net.WebClient).DownloadFile('https://www.python.org/downloads/windows/','%TEMP%\pyurl.txt')" 229 | REM Extract it if it's gzip compressed 230 | powershell -command "$infile='%TEMP%\pyurl.txt';$outfile='%TEMP%\pyurl.temp';try{$input=New-Object System.IO.FileStream $infile,([IO.FileMode]::Open),([IO.FileAccess]::Read),([IO.FileShare]::Read);$output=New-Object System.IO.FileStream $outfile,([IO.FileMode]::Create),([IO.FileAccess]::Write),([IO.FileShare]::None);$gzipStream=New-Object System.IO.Compression.GzipStream $input,([IO.Compression.CompressionMode]::Decompress);$buffer=New-Object byte[](1024);while($true){$read=$gzipstream.Read($buffer,0,1024);if($read -le 0){break};$output.Write($buffer,0,$read)};$gzipStream.Close();$output.Close();$input.Close();Move-Item -Path $outfile -Destination $infile -Force}catch{}" 231 | if not exist "%TEMP%\pyurl.txt" ( 232 | if /i "!just_installing!" == "TRUE" ( 233 | echo Failed to get info 234 | exit /b 1 235 | ) else ( 236 | goto checkpy 237 | ) 238 | ) 239 | echo Parsing for latest... 240 | pushd "%TEMP%" 241 | :: Version detection code slimmed by LussacZheng (https://github.com/corpnewt/gibMacOS/issues/20) 242 | for /f "tokens=9 delims=< " %%x in ('findstr /i /c:"Latest Python !targetpy! Release" pyurl.txt') do ( set "release=%%x" ) 243 | popd 244 | if "!release!" == "" ( 245 | if /i "!just_installing!" == "TRUE" ( 246 | echo Failed to get python version 247 | exit /b 1 248 | ) else ( 249 | goto checkpy 250 | ) 251 | ) 252 | echo Found Python !release! - Downloading... 253 | REM Let's delete our txt file now - we no longer need it 254 | del "%TEMP%\pyurl.txt" 255 | REM At this point - we should have the version number. 256 | REM We can build the url like so: "https://www.python.org/ftp/python/[version]/python-[version]-amd64.exe" 257 | set "url=https://www.python.org/ftp/python/!release!/python-!release!-amd64.exe" 258 | set "pytype=exe" 259 | if "!targetpy!" == "2" ( 260 | set "url=https://www.python.org/ftp/python/!release!/python-!release!.amd64.msi" 261 | set "pytype=msi" 262 | ) 263 | REM Now we download it with our slick powershell command 264 | powershell -command "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; (new-object System.Net.WebClient).DownloadFile('!url!','%TEMP%\pyinstall.!pytype!')" 265 | REM If it doesn't exist - we bail 266 | if not exist "%TEMP%\pyinstall.!pytype!" ( 267 | if /i "!just_installing!" == "TRUE" ( 268 | echo Failed to download installer 269 | exit /b 1 270 | ) else ( 271 | goto checkpy 272 | ) 273 | ) 274 | REM It should exist at this point - let's run it to install silently 275 | echo Installing... 276 | pushd "%TEMP%" 277 | if /i "!pytype!" == "exe" ( 278 | echo pyinstall.exe /quiet PrependPath=1 Include_test=0 Shortcuts=0 Include_launcher=0 279 | pyinstall.exe /quiet PrependPath=1 Include_test=0 Shortcuts=0 Include_launcher=0 280 | ) else ( 281 | set "foldername=!release:.=!" 282 | echo msiexec /i pyinstall.msi /qb ADDLOCAL=ALL TARGETDIR="%LocalAppData%\Programs\Python\Python!foldername:~0,2!" 283 | msiexec /i pyinstall.msi /qb ADDLOCAL=ALL TARGETDIR="%LocalAppData%\Programs\Python\Python!foldername:~0,2!" 284 | ) 285 | popd 286 | echo Installer finished with %ERRORLEVEL% status. 287 | REM Now we should be able to delete the installer and check for py again 288 | del "%TEMP%\pyinstall.!pytype!" 289 | REM If it worked, then we should have python in our PATH 290 | REM this does not get updated right away though - let's try 291 | REM manually updating the local PATH var 292 | call :updatepath 293 | if /i "!just_installing!" == "TRUE" ( 294 | echo. 295 | echo Done. 296 | ) else ( 297 | goto checkpy 298 | ) 299 | exit /b 300 | 301 | :runscript 302 | REM Python found 303 | cls 304 | set "args=%*" 305 | set "args=!args:"=!" 306 | if "!args!"=="" ( 307 | "!pypath!" "!thisDir!!script_name!" 308 | ) else ( 309 | "!pypath!" "!thisDir!!script_name!" %* 310 | ) 311 | if /i "!pause_on_error!" == "yes" ( 312 | if not "%ERRORLEVEL%" == "0" ( 313 | echo. 314 | echo Script exited with error code: %ERRORLEVEL% 315 | echo. 316 | echo Press [enter] to exit... 317 | pause > nul 318 | ) 319 | ) 320 | goto :EOF 321 | 322 | :undouble 323 | REM Helper function to strip doubles of a single character out of a string recursively 324 | set "string_value=%~2" 325 | :undouble_continue 326 | set "check=!string_value:%~3%~3=%~3!" 327 | if not "!check!" == "!string_value!" ( 328 | set "string_value=!check!" 329 | goto :undouble_continue 330 | ) 331 | set "%~1=!check!" 332 | goto :EOF 333 | 334 | :updatepath 335 | set "spath=" 336 | set "upath=" 337 | for /f "USEBACKQ tokens=2* delims= " %%i in (`!syspath!reg.exe query "HKCU\Environment" /v "Path" 2^> nul`) do ( if not "%%j" == "" set "upath=%%j" ) 338 | for /f "USEBACKQ tokens=2* delims= " %%i in (`!syspath!reg.exe query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v "Path" 2^> nul`) do ( if not "%%j" == "" set "spath=%%j" ) 339 | if not "%spath%" == "" ( 340 | REM We got something in the system path 341 | set "PATH=%spath%" 342 | if not "%upath%" == "" ( 343 | REM We also have something in the user path 344 | set "PATH=%PATH%;%upath%" 345 | ) 346 | ) else if not "%upath%" == "" ( 347 | set "PATH=%upath%" 348 | ) 349 | REM Remove double semicolons from the adjusted PATH 350 | call :undouble "PATH" "%PATH%" ";" 351 | goto :EOF 352 | 353 | :getsyspath 354 | REM Helper method to return a valid path to cmd.exe, reg.exe, and where.exe by 355 | REM walking the ComSpec var - will also repair it in memory if need be 356 | REM Strip double semi-colons 357 | call :undouble "temppath" "%ComSpec%" ";" 358 | 359 | REM Dirty hack to leverage the "line feed" approach - there are some odd side 360 | REM effects with this. Do not use this variable name in comments near this 361 | REM line - as it seems to behave erradically. 362 | (set LF=^ 363 | %=this line is empty=% 364 | ) 365 | REM Replace instances of semi-colons with a line feed and wrap 366 | REM in parenthesis to work around some strange batch behavior 367 | set "testpath=%temppath:;=!LF!%" 368 | 369 | REM Let's walk each path and test if cmd.exe, reg.exe, and where.exe exist there 370 | set /a found=0 371 | for /f "tokens=* delims=" %%i in ("!testpath!") do ( 372 | REM Only continue if we haven't found it yet 373 | if not "%%i" == "" ( 374 | if !found! lss 1 ( 375 | set "checkpath=%%i" 376 | REM Remove "cmd.exe" from the end if it exists 377 | if /i "!checkpath:~-7!" == "cmd.exe" ( 378 | set "checkpath=!checkpath:~0,-7!" 379 | ) 380 | REM Pad the end with a backslash if needed 381 | if not "!checkpath:~-1!" == "\" ( 382 | set "checkpath=!checkpath!\" 383 | ) 384 | REM Let's see if cmd, reg, and where exist there - and set it if so 385 | if EXIST "!checkpath!cmd.exe" ( 386 | if EXIST "!checkpath!reg.exe" ( 387 | if EXIST "!checkpath!where.exe" ( 388 | set /a found=1 389 | set "ComSpec=!checkpath!cmd.exe" 390 | set "%~1=!checkpath!" 391 | ) 392 | ) 393 | ) 394 | ) 395 | ) 396 | ) 397 | goto :EOF 398 | -------------------------------------------------------------------------------- /GenSMBIOS.command: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Get the curent directory, the script name 4 | # and the script name with "py" substituted for the extension. 5 | args=( "$@" ) 6 | dir="$(cd -- "$(dirname "$0")" >/dev/null 2>&1; pwd -P)" 7 | script="${0##*/}" 8 | target="${script%.*}.py" 9 | 10 | # use_py3: 11 | # TRUE = Use if found, use py2 otherwise 12 | # FALSE = Use py2 13 | # FORCE = Use py3 14 | use_py3="TRUE" 15 | 16 | # We'll parse if the first argument passed is 17 | # --install-python and if so, we'll just install 18 | just_installing="FALSE" 19 | 20 | tempdir="" 21 | 22 | compare_to_version () { 23 | # Compares our OS version to the passed OS version, and 24 | # return a 1 if we match the passed compare type, or a 0 if we don't. 25 | # $1 = 0 (equal), 1 (greater), 2 (less), 3 (gequal), 4 (lequal) 26 | # $2 = OS version to compare ours to 27 | if [ -z "$1" ] || [ -z "$2" ]; then 28 | # Missing info - bail. 29 | return 30 | fi 31 | local current_os= comp= 32 | current_os="$(sw_vers -productVersion)" 33 | comp="$(vercomp "$current_os" "$2")" 34 | # Check gequal and lequal first 35 | if [[ "$1" == "3" && ("$comp" == "1" || "$comp" == "0") ]] || [[ "$1" == "4" && ("$comp" == "2" || "$comp" == "0") ]] || [[ "$comp" == "$1" ]]; then 36 | # Matched 37 | echo "1" 38 | else 39 | # No match 40 | echo "0" 41 | fi 42 | } 43 | 44 | set_use_py3_if () { 45 | # Auto sets the "use_py3" variable based on 46 | # conditions passed 47 | # $1 = 0 (equal), 1 (greater), 2 (less), 3 (gequal), 4 (lequal) 48 | # $2 = OS version to compare 49 | # $3 = TRUE/FALSE/FORCE in case of match 50 | if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ]; then 51 | # Missing vars - bail with no changes. 52 | return 53 | fi 54 | if [ "$(compare_to_version "$1" "$2")" == "1" ]; then 55 | use_py3="$3" 56 | fi 57 | } 58 | 59 | get_remote_py_version () { 60 | local pyurl= py_html= py_vers= py_num="3" 61 | pyurl="https://www.python.org/downloads/macos/" 62 | py_html="$(curl -L $pyurl --compressed 2>&1)" 63 | if [ -z "$use_py3" ]; then 64 | use_py3="TRUE" 65 | fi 66 | if [ "$use_py3" == "FALSE" ]; then 67 | py_num="2" 68 | fi 69 | py_vers="$(echo "$py_html" | grep -i "Latest Python $py_num Release" | awk '{print $8}' | cut -d'<' -f1)" 70 | echo "$py_vers" 71 | } 72 | 73 | download_py () { 74 | local vers="$1" url= 75 | clear 76 | echo " ### ###" 77 | echo " # Downloading Python #" 78 | echo "### ###" 79 | echo 80 | if [ -z "$vers" ]; then 81 | echo "Gathering latest version..." 82 | vers="$(get_remote_py_version)" 83 | fi 84 | if [ -z "$vers" ]; then 85 | # Didn't get it still - bail 86 | print_error 87 | fi 88 | echo "Located Version: $vers" 89 | echo 90 | echo "Building download url..." 91 | url="$(curl -L https://www.python.org/downloads/release/python-${vers//./}/ --compressed 2>&1 | grep -iE "python-$vers-macos.*.pkg\"" | awk -F'"' '{ print $2 }')" 92 | if [ -z "$url" ]; then 93 | # Couldn't get the URL - bail 94 | print_error 95 | fi 96 | echo " - $url" 97 | echo 98 | echo "Downloading..." 99 | echo 100 | # Create a temp dir and download to it 101 | tempdir="$(mktemp -d 2>/dev/null || mktemp -d -t 'tempdir')" 102 | curl "$url" -o "$tempdir/python.pkg" 103 | if [ "$?" != "0" ]; then 104 | echo 105 | echo " - Failed to download python installer!" 106 | echo 107 | exit $? 108 | fi 109 | echo 110 | echo "Running python install package..." 111 | echo 112 | sudo installer -pkg "$tempdir/python.pkg" -target / 113 | if [ "$?" != "0" ]; then 114 | echo 115 | echo " - Failed to install python!" 116 | echo 117 | exit $? 118 | fi 119 | # Now we expand the package and look for a shell update script 120 | pkgutil --expand "$tempdir/python.pkg" "$tempdir/python" 121 | if [ -e "$tempdir/python/Python_Shell_Profile_Updater.pkg/Scripts/postinstall" ]; then 122 | # Run the script 123 | echo 124 | echo "Updating PATH..." 125 | echo 126 | "$tempdir/python/Python_Shell_Profile_Updater.pkg/Scripts/postinstall" 127 | fi 128 | vers_folder="Python $(echo "$vers" | cut -d'.' -f1 -f2)" 129 | if [ -f "/Applications/$vers_folder/Install Certificates.command" ]; then 130 | # Certs script exists - let's execute that to make sure our certificates are updated 131 | echo 132 | echo "Updating Certificates..." 133 | echo 134 | "/Applications/$vers_folder/Install Certificates.command" 135 | fi 136 | echo 137 | echo "Cleaning up..." 138 | cleanup 139 | echo 140 | if [ "$just_installing" == "TRUE" ]; then 141 | echo "Done." 142 | else 143 | # Now we check for py again 144 | echo "Rechecking py..." 145 | downloaded="TRUE" 146 | clear 147 | main 148 | fi 149 | } 150 | 151 | cleanup () { 152 | if [ -d "$tempdir" ]; then 153 | rm -Rf "$tempdir" 154 | fi 155 | } 156 | 157 | print_error() { 158 | clear 159 | cleanup 160 | echo " ### ###" 161 | echo " # Python Not Found #" 162 | echo "### ###" 163 | echo 164 | echo "Python is not installed or not found in your PATH var." 165 | echo 166 | if [ "$kernel" == "Darwin" ]; then 167 | echo "Please go to https://www.python.org/downloads/macos/ to" 168 | echo "download and install the latest version, then try again." 169 | else 170 | echo "Please install python through your package manager and" 171 | echo "try again." 172 | fi 173 | echo 174 | exit 1 175 | } 176 | 177 | print_target_missing() { 178 | clear 179 | cleanup 180 | echo " ### ###" 181 | echo " # Target Not Found #" 182 | echo "### ###" 183 | echo 184 | echo "Could not locate $target!" 185 | echo 186 | exit 1 187 | } 188 | 189 | format_version () { 190 | local vers="$1" 191 | echo "$(echo "$1" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }')" 192 | } 193 | 194 | vercomp () { 195 | # Modified from: https://apple.stackexchange.com/a/123408/11374 196 | local ver1="$(format_version "$1")" ver2="$(format_version "$2")" 197 | if [ $ver1 -gt $ver2 ]; then 198 | echo "1" 199 | elif [ $ver1 -lt $ver2 ]; then 200 | echo "2" 201 | else 202 | echo "0" 203 | fi 204 | } 205 | 206 | get_local_python_version() { 207 | # $1 = Python bin name (defaults to python3) 208 | # Echoes the path to the highest version of the passed python bin if any 209 | local py_name="$1" max_version= python= python_version= python_path= 210 | if [ -z "$py_name" ]; then 211 | py_name="python3" 212 | fi 213 | py_list="$(which -a "$py_name" 2>/dev/null)" 214 | # Walk that newline separated list 215 | while read python; do 216 | if [ -z "$python" ]; then 217 | # Got a blank line - skip 218 | continue 219 | fi 220 | if [ "$check_py3_stub" == "1" ] && [ "$python" == "/usr/bin/python3" ]; then 221 | # See if we have a valid developer path 222 | xcode-select -p > /dev/null 2>&1 223 | if [ "$?" != "0" ]; then 224 | # /usr/bin/python3 path - but no valid developer dir 225 | continue 226 | fi 227 | fi 228 | python_version="$(get_python_version $python)" 229 | if [ -z "$python_version" ]; then 230 | # Didn't find a py version - skip 231 | continue 232 | fi 233 | # Got the py version - compare to our max 234 | if [ -z "$max_version" ] || [ "$(vercomp "$python_version" "$max_version")" == "1" ]; then 235 | # Max not set, or less than the current - update it 236 | max_version="$python_version" 237 | python_path="$python" 238 | fi 239 | done <<< "$py_list" 240 | echo "$python_path" 241 | } 242 | 243 | get_python_version() { 244 | local py_path="$1" py_version= 245 | # Get the python version by piping stderr into stdout (for py2), then grepping the output for 246 | # the word "python", getting the second element, and grepping for an alphanumeric version number 247 | py_version="$($py_path -V 2>&1 | grep -i python | cut -d' ' -f2 | grep -E "[A-Za-z\d\.]+")" 248 | if [ ! -z "$py_version" ]; then 249 | echo "$py_version" 250 | fi 251 | } 252 | 253 | prompt_and_download() { 254 | if [ "$downloaded" != "FALSE" ] || [ "$kernel" != "Darwin" ]; then 255 | # We already tried to download, or we're not on macOS - just bail 256 | print_error 257 | fi 258 | clear 259 | echo " ### ###" 260 | echo " # Python Not Found #" 261 | echo "### ###" 262 | echo 263 | target_py="Python 3" 264 | printed_py="Python 2 or 3" 265 | if [ "$use_py3" == "FORCE" ]; then 266 | printed_py="Python 3" 267 | elif [ "$use_py3" == "FALSE" ]; then 268 | target_py="Python 2" 269 | printed_py="Python 2" 270 | fi 271 | echo "Could not locate $printed_py!" 272 | echo 273 | echo "This script requires $printed_py to run." 274 | echo 275 | while true; do 276 | read -p "Would you like to install the latest $target_py now? (y/n): " yn 277 | case $yn in 278 | [Yy]* ) download_py;break;; 279 | [Nn]* ) print_error;; 280 | esac 281 | done 282 | } 283 | 284 | main() { 285 | local python= version= 286 | # Verify our target exists 287 | if [ ! -f "$dir/$target" ]; then 288 | # Doesn't exist 289 | print_target_missing 290 | fi 291 | if [ -z "$use_py3" ]; then 292 | use_py3="TRUE" 293 | fi 294 | if [ "$use_py3" != "FALSE" ]; then 295 | # Check for py3 first 296 | python="$(get_local_python_version python3)" 297 | fi 298 | if [ "$use_py3" != "FORCE" ] && [ -z "$python" ]; then 299 | # We aren't using py3 explicitly, and we don't already have a path 300 | python="$(get_local_python_version python2)" 301 | if [ -z "$python" ]; then 302 | # Try just looking for "python" 303 | python="$(get_local_python_version python)" 304 | fi 305 | fi 306 | if [ -z "$python" ]; then 307 | # Didn't ever find it - prompt 308 | prompt_and_download 309 | return 1 310 | fi 311 | # Found it - start our script and pass all args 312 | "$python" "$dir/$target" "${args[@]}" 313 | } 314 | 315 | # Keep track of whether or not we're on macOS to determine if 316 | # we can download and install python for the user as needed. 317 | kernel="$(uname -s)" 318 | # Check to see if we need to force based on 319 | # macOS version. 10.15 has a dummy python3 version 320 | # that can trip up some py3 detection in other scripts. 321 | # set_use_py3_if "3" "10.15" "FORCE" 322 | downloaded="FALSE" 323 | # Check for the aforementioned /usr/bin/python3 stub if 324 | # our OS version is 10.15 or greater. 325 | check_py3_stub="$(compare_to_version "3" "10.15")" 326 | trap cleanup EXIT 327 | if [ "$1" == "--install-python" ] && [ "$kernel" == "Darwin" ]; then 328 | just_installing="TRUE" 329 | download_py 330 | else 331 | main 332 | fi 333 | -------------------------------------------------------------------------------- /GenSMBIOS.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os, subprocess, shlex, sys, tempfile, shutil, random, uuid, zipfile, json, binascii 3 | from Scripts import downloader, plist, run, utils 4 | from collections import OrderedDict 5 | # Import from secrets - or fall back on random.SystemRandom() 6 | # functions if on python 2 7 | try: 8 | from secrets import randbits, choice 9 | basestring = str 10 | except ImportError: 11 | from random import SystemRandom 12 | _sysrand = SystemRandom() 13 | randbits = _sysrand.getrandbits 14 | choice = _sysrand.choice 15 | 16 | class Smbios: 17 | def __init__(self): 18 | os.chdir(os.path.dirname(os.path.realpath(__file__))) 19 | self.u = utils.Utils("GenSMBIOS") 20 | self.d = downloader.Downloader() 21 | self.r = run.Run() 22 | self.oc_release_url = "https://github.com/acidanthera/OpenCorePkg/releases/latest" 23 | self.scripts = "Scripts" 24 | self.plist = None 25 | self.plist_data = None 26 | self.plist_type = "Unknown" # Can be "Clover" or "OpenCore" depending 27 | self.remote = self._get_remote_version() 28 | self.okay_keys = [ 29 | "SerialNumber", 30 | "BoardSerialNumber", 31 | "SmUUID", 32 | "ProductName", 33 | "Trust", 34 | "Memory" 35 | ] 36 | try: self.rom_prefixes = json.load(open(os.path.join(self.scripts,"prefix.json"))) 37 | except: self.rom_prefixes = [] 38 | self.settings_file = os.path.join(self.scripts,"settings.json") 39 | try: self.settings = json.load(open(self.settings_file)) 40 | except: self.settings = {} 41 | self.gen_rom = True 42 | 43 | def _save_settings(self): 44 | if self.settings: 45 | try: 46 | json.dump(self.settings,open(self.settings_file,"w"),indent=2) 47 | except: 48 | pass 49 | elif os.path.exists(self.settings_file): 50 | try: 51 | os.remove(self.settings_file) 52 | except: 53 | pass 54 | 55 | def _get_macserial_version(self): 56 | # Attempts to determine the macserial version from the latest OpenCorePkg 57 | macserial_v = None 58 | try: 59 | urlsource = self.d.get_string(self.oc_release_url, False) 60 | for line in urlsource.split("\n"): 61 | if "expanded_assets" in line: 62 | # Get the version from the URL 63 | oc_vers = line.split(' src="')[1].split('"')[0].split("/")[-1] 64 | macserial_h_url = "https://raw.githubusercontent.com/acidanthera/OpenCorePkg/{}/Utilities/macserial/macserial.h".format(oc_vers) 65 | macserial_h = self.d.get_string(macserial_h_url,False) 66 | macserial_v = macserial_h.split('#define PROGRAM_VERSION "')[1].split('"')[0] 67 | except: 68 | pass 69 | return macserial_v 70 | 71 | def _get_macserial_url(self): 72 | # Gets a URL to the latest release of OpenCorePkg 73 | try: 74 | urlsource = self.d.get_string(self.oc_release_url, False) 75 | for line in urlsource.split("\n"): 76 | if "expanded_assets" in line: 77 | expanded_html = self.d.get_string(line.split(' src="')[1].split('"')[0], False) 78 | for l in expanded_html.split("\n"): 79 | if 'href="/acidanthera/OpenCorePkg/releases/download/' in l and "-RELEASE.zip" in l: 80 | # Got it 81 | return "https://github.com{}".format(l.split('href="')[1].split('"')[0]) 82 | except: 83 | pass 84 | return None 85 | 86 | def _get_binary(self,binary_name=None): 87 | if not binary_name: 88 | binary_name = ["macserial.exe","macserial32.exe"] if os.name == "nt" else ["macserial.linux","macserial"] if sys.platform.startswith("linux") else ["macserial"] 89 | # Check locally 90 | cwd = os.getcwd() 91 | os.chdir(os.path.dirname(os.path.realpath(__file__))) 92 | path = None 93 | for name in binary_name: 94 | if os.path.exists(name): 95 | path = os.path.join(os.getcwd(), name) 96 | elif os.path.exists(os.path.join(os.getcwd(), self.scripts, name)): 97 | path = os.path.join(os.getcwd(),self.scripts,name) 98 | if path: break # Found it, bail 99 | os.chdir(cwd) 100 | return path 101 | 102 | def _get_version(self,macserial): 103 | # Gets the macserial version 104 | out, error, code = self.r.run({"args":[macserial]}) 105 | if not len(out): 106 | return None 107 | for line in out.split("\n"): 108 | if not line.lower().startswith("version"): 109 | continue 110 | vers = next((x for x in line.lower().strip().split() if len(x) and x[0] in "0123456789"),None) 111 | if not vers is None and vers[-1] == ".": 112 | vers = vers[:-1] 113 | return vers 114 | return None 115 | 116 | def _download_and_extract(self, temp, url, path_in_zip=[]): 117 | ztemp = tempfile.mkdtemp(dir=temp) 118 | zfile = os.path.basename(url) 119 | print("\nDownloading {}...".format(os.path.basename(url))) 120 | result = self.d.stream_to_file(url, os.path.join(ztemp,zfile)) 121 | print("") 122 | if not result: 123 | raise Exception(" - Failed to download!") 124 | print(" - Extracting...") 125 | btemp = tempfile.mkdtemp(dir=temp) 126 | # Extract with built-in tools \o/ 127 | with zipfile.ZipFile(os.path.join(ztemp,zfile)) as z: 128 | z.extractall(os.path.join(temp,btemp)) 129 | script_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)),self.scripts) 130 | search_path = os.path.join(temp,btemp) 131 | # Extend the search path if path_in_zip contains elements 132 | if path_in_zip: search_path = os.path.join(search_path,*path_in_zip) 133 | for x in os.listdir(search_path): 134 | if "macserial" in x.lower(): 135 | # Found one 136 | print(" - Found {}".format(x)) 137 | if os.name != "nt": 138 | print(" - Chmod +x...") 139 | self.r.run({"args":["chmod","+x",os.path.join(search_path,x)]}) 140 | print(" - Copying to {} directory...".format(self.scripts)) 141 | if not os.path.exists(script_dir): 142 | os.mkdir(script_dir) 143 | shutil.copy(os.path.join(search_path,x), os.path.join(script_dir,x)) 144 | 145 | def _get_macserial(self): 146 | # Download both the windows and mac versions of macserial and expand them to the Scripts dir 147 | self.u.head("Getting MacSerial") 148 | print("") 149 | print("Gathering latest macserial info...") 150 | url = self._get_macserial_url() 151 | path_in_zip = ["Utilities","macserial"] 152 | if not url: 153 | print("Error checking for updates (network issue)\n") 154 | self.u.grab("Press [enter] to return...") 155 | return 156 | temp = tempfile.mkdtemp() 157 | cwd = os.getcwd() 158 | try: 159 | print(" - {}".format(url)) 160 | self._download_and_extract(temp,url,path_in_zip) 161 | except Exception as e: 162 | print("We ran into some problems :(\n\n{}".format(e)) 163 | print("\nCleaning up...") 164 | os.chdir(cwd) 165 | shutil.rmtree(temp) 166 | self.u.grab("\nDone.",timeout=5) 167 | return 168 | 169 | def _get_remote_version(self): 170 | self.u.head("Getting MacSerial Remote Version") 171 | print("") 172 | print("Gathering latest macserial info...") 173 | print(" - Gathering info from OpenCorePkg...") 174 | vers = self._get_macserial_version() 175 | if not vers: 176 | print("Error checking for updates (network issue)\n") 177 | self.u.grab("Press [enter] to return...") 178 | return None 179 | return vers 180 | 181 | def _get_plist(self): 182 | self.u.head("Select Plist") 183 | print("") 184 | print("Current: {}".format(self.plist)) 185 | print("Type: {}".format(self.plist_type)) 186 | print("") 187 | print("C. Clear Selection") 188 | print("M. Main Menu") 189 | print("Q. Quit") 190 | print("") 191 | p = self.u.grab("Please drag and drop the target plist: ") 192 | if p.lower() == "q": 193 | self.u.custom_quit() 194 | elif p.lower() == "m": 195 | return 196 | elif p.lower() == "c": 197 | self.plist = None 198 | self.plist_data = None 199 | return 200 | 201 | pc = self.u.check_path(p) 202 | if not pc: 203 | self.u.head("File Missing") 204 | print("") 205 | print("Plist file not found:\n\n{}".format(p)) 206 | print("") 207 | self.u.grab("Press [enter] to return...") 208 | return self._get_plist() 209 | try: 210 | with open(pc, "rb") as f: 211 | self.plist_data = plist.load(f,dict_type=OrderedDict) 212 | except Exception as e: 213 | self.u.head("Plist Malformed") 214 | print("") 215 | print("Plist file malformed:\n\n{}".format(e)) 216 | print("") 217 | self.u.grab("Press [enter] to return...") 218 | return self._get_plist() 219 | # Got a valid plist - let's try to check for Clover or OC structure 220 | detected_type = "OpenCore" if "PlatformInfo" in self.plist_data else "Clover" if "SMBIOS" in self.plist_data else "Unknown" 221 | if detected_type.lower() == "unknown": 222 | # Have the user decide which to do 223 | while True: 224 | self.u.head("Unknown Plist Type") 225 | print("") 226 | print("Could not auto-determine plist type!") 227 | print("") 228 | print("1. Clover") 229 | print("2. OpenCore") 230 | print("") 231 | print("M. Return to the Menu") 232 | print("") 233 | t = self.u.grab("Please select the target type: ").lower() 234 | if t == "m": return self._get_plist() 235 | elif t in ("1","2"): 236 | detected_type = "Clover" if t == "1" else "OpenCore" 237 | break 238 | # Got a plist and type - let's save it 239 | self.plist_type = detected_type 240 | # Apply any key-stripping or safety checks 241 | if self.plist_type.lower() == "clover": 242 | # Got a valid clover plist - let's check keys 243 | key_check = self.plist_data.get("SMBIOS",{}) 244 | new_smbios = {} 245 | removed_keys = [] 246 | for key in key_check: 247 | if key not in self.okay_keys: 248 | removed_keys.append(key) 249 | else: 250 | # Build our new SMBIOS 251 | new_smbios[key] = key_check[key] 252 | # We want the SmUUID to be the top-level - remove CustomUUID if exists 253 | if "CustomUUID" in self.plist_data.get("SystemParameters",{}): 254 | removed_keys.append("CustomUUID") 255 | if len(removed_keys): 256 | while True: 257 | self.u.head("") 258 | print("") 259 | print("The following keys will be removed:\n\n{}\n".format(", ".join(removed_keys))) 260 | con = self.u.grab("Continue? (y/n): ") 261 | if con.lower() == "y": 262 | # Flush settings 263 | self.plist_data["SMBIOS"] = new_smbios 264 | # Remove the CustomUUID if present 265 | self.plist_data.get("SystemParameters",{}).pop("CustomUUID", None) 266 | break 267 | elif con.lower() == "n": 268 | self.plist_data = None 269 | return 270 | self.plist = pc 271 | 272 | def _get_rom(self): 273 | # Generate 6-bytes of cryptographically random values 274 | rom_str = "{:x}".format(randbits(8*6)).upper().rjust(12,"0") 275 | if self.rom_prefixes: 276 | # Replace the prefix with one from our list 277 | prefix = choice(self.rom_prefixes) 278 | if isinstance(prefix,basestring): 279 | rom_str = prefix+rom_str[len(prefix):] 280 | return rom_str 281 | 282 | def _get_smbios(self, macserial, smbios_type, times=1): 283 | # Returns a list of SMBIOS lines that match 284 | total = [] 285 | # Get any additional args and ensure they're a string 286 | args = self.settings.get("macserial_args") 287 | if not isinstance(args,basestring): args = "" 288 | while len(total) < times: 289 | total_len = len(total) 290 | smbios, err, code = self.r.run({"args":[macserial,"-a"]+shlex.split(args)}) 291 | if code != 0: 292 | # Issues generating 293 | return None 294 | # Got our text, let's see if the SMBIOS exists 295 | for line in smbios.split("\n"): 296 | line = line.strip() 297 | if line.lower().startswith(smbios_type.lower()): 298 | total.append(line) 299 | if len(total) >= times: 300 | break 301 | if total_len == len(total): 302 | # Total didn't change - return False 303 | return False 304 | # Have a list now - let's format it 305 | output = [] 306 | for sm in total: 307 | s_list = [x.strip() for x in sm.split("|")] 308 | # Add a uuid 309 | s_list.append(str(uuid.uuid4()).upper()) 310 | # Generate a ROM value 311 | s_list.append(self._get_rom()) 312 | # Format the text 313 | output.append(s_list) 314 | return output 315 | 316 | def _generate_smbios(self, macserial): 317 | if not macserial or not os.path.exists(macserial): 318 | # Attempt to download 319 | self._get_macserial() 320 | # Check it again 321 | macserial = self._get_binary() 322 | if not macserial or not os.path.exists(macserial): 323 | # Could not find it, and it failed to download :( 324 | self.u.head("Missing MacSerial") 325 | print("") 326 | print("MacSerial binary was not found and failed to download.") 327 | print("") 328 | self.u.grab("Press [enter] to return...") 329 | return 330 | self.u.head("Generate SMBIOS") 331 | print("") 332 | print("M. Main Menu") 333 | print("Q. Quit") 334 | print("") 335 | print("Please type the SMBIOS to gen and the number") 336 | menu = self.u.grab("of times to generate [max 20] (i.e. iMac18,3 5): ") 337 | if menu.lower() == "q": 338 | self.u.custom_quit() 339 | elif menu.lower() == "m": 340 | return 341 | menu = menu.split(" ") 342 | if len(menu) == 1: 343 | # Default of one time 344 | smtype = menu[0] 345 | times = 1 346 | else: 347 | smtype = menu[0] 348 | try: 349 | times = int(menu[1]) 350 | except: 351 | self.u.head("Incorrect Input") 352 | print("") 353 | print("Incorrect format - must be SMBIOS times - i.e. iMac18,3 5") 354 | print("") 355 | self.u.grab("Press [enter] to return...") 356 | self._generate_smbios(macserial) 357 | return 358 | # Keep it between 1 and 20 359 | if times < 1: 360 | times = 1 361 | if times > 20: 362 | times = 20 363 | smbios = self._get_smbios(macserial,smtype,times) 364 | if smbios is None: 365 | # Issues generating 366 | print("Error - macserial returned an error!") 367 | self.u.grab("Press [enter] to return...") 368 | return 369 | if smbios == False: 370 | print("\nError - {} not generated by macserial\n".format(smtype)) 371 | self.u.grab("Press [enter] to return...") 372 | return 373 | self.u.head("{} SMBIOS Info".format(smbios[0][0])) 374 | print("") 375 | if self.settings.get("macserial_args") and isinstance(self.settings["macserial_args"],basestring): 376 | print("Additional Arguments Passed:") 377 | print(" {}".format(self.settings["macserial_args"])) 378 | print("") 379 | f_string = "Type: {}\nSerial: {}\nBoard Serial: {}\nSmUUID: {}" 380 | if self.gen_rom: f_string += "\nApple ROM: {}" if self.rom_prefixes else "\nRandom ROM: {}" 381 | print("\n\n".join([f_string.format(*x) for x in smbios])) 382 | if self.plist_data and self.plist and os.path.exists(self.plist): 383 | # Let's apply - got a valid file, and plist data 384 | if len(smbios) > 1: 385 | print("\nFlushing first SMBIOS entry to {}".format(self.plist)) 386 | else: 387 | print("\nFlushing SMBIOS entry to {}".format(self.plist)) 388 | if self.plist_type.lower() == "clover": 389 | # Ensure plist data exists 390 | for x in ["SMBIOS","RtVariables","SystemParameters"]: 391 | if not x in self.plist_data: 392 | self.plist_data[x] = {} 393 | self.plist_data["SMBIOS"]["ProductName"] = smbios[0][0] 394 | self.plist_data["SMBIOS"]["SerialNumber"] = smbios[0][1] 395 | self.plist_data["SMBIOS"]["BoardSerialNumber"] = smbios[0][2] 396 | self.plist_data["RtVariables"]["MLB"] = smbios[0][2] 397 | self.plist_data["SMBIOS"]["SmUUID"] = smbios[0][3] 398 | if self.gen_rom: 399 | self.plist_data["RtVariables"]["ROM"] = plist.wrap_data(binascii.unhexlify(smbios[0][4].encode("utf-8"))) 400 | self.plist_data["SystemParameters"]["InjectSystemID"] = True 401 | elif self.plist_type.lower() == "opencore": 402 | # Ensure data exists 403 | if not "PlatformInfo" in self.plist_data: self.plist_data["PlatformInfo"] = {} 404 | if not "Generic" in self.plist_data["PlatformInfo"]: self.plist_data["PlatformInfo"]["Generic"] = {} 405 | # Set the values 406 | self.plist_data["PlatformInfo"]["Generic"]["SystemProductName"] = smbios[0][0] 407 | self.plist_data["PlatformInfo"]["Generic"]["SystemSerialNumber"] = smbios[0][1] 408 | self.plist_data["PlatformInfo"]["Generic"]["MLB"] = smbios[0][2] 409 | self.plist_data["PlatformInfo"]["Generic"]["SystemUUID"] = smbios[0][3] 410 | if self.gen_rom: 411 | self.plist_data["PlatformInfo"]["Generic"]["ROM"] = plist.wrap_data(binascii.unhexlify(smbios[0][4].encode("utf-8"))) 412 | with open(self.plist, "wb") as f: 413 | plist.dump(self.plist_data, f, sort_keys=False) 414 | # Got only valid keys now 415 | print("") 416 | self.u.grab("Press [enter] to return...") 417 | 418 | def _list_current(self, macserial): 419 | if not macserial or not os.path.exists(macserial): 420 | self.u.head("Missing MacSerial") 421 | print("") 422 | print("MacSerial binary not found.") 423 | print("") 424 | self.u.grab("Press [enter] to return...") 425 | return 426 | out, err, code = self.r.run({"args":[macserial]}) 427 | out = "\n".join([x for x in out.split("\n") if not x.lower().startswith("version") and len(x)]) 428 | self.u.head("Current SMBIOS Info") 429 | print("") 430 | print(out) 431 | print("") 432 | self.u.grab("Press [enter] to return...") 433 | 434 | def get_additional_args(self): 435 | while True: 436 | self.u.head("Additional Arguments") 437 | print("") 438 | print("Current Additional Arguments:") 439 | args = self.settings.get("macserial_args") 440 | if not args or not isinstance(args,basestring): args = None 441 | print(" {}".format(args)) 442 | print("") 443 | print("The -a argument is always passed to macserial, but you can enter additional") 444 | print("arguments to fine-tune SMBIOS generation.") 445 | print("") 446 | print("C. Clear Additional Arguments") 447 | print("M. Return To Main Menu") 448 | print("Q. Quit") 449 | print("") 450 | args = self.u.grab("Please type the arguments to pass: ") 451 | if not len(args): 452 | continue 453 | elif args.lower() == "m": 454 | return 455 | elif args.lower() == "q": 456 | self.u.custom_quit() 457 | elif args.lower() == "c": 458 | self.settings.pop("macserial_args",None) 459 | self._save_settings() 460 | else: 461 | self.settings["macserial_args"] = args 462 | self._save_settings() 463 | 464 | def main(self): 465 | self.u.head() 466 | print("") 467 | macserial = self._get_binary() 468 | if macserial: 469 | macserial_v = self._get_version(macserial) 470 | print("MacSerial v{}".format(macserial_v)) 471 | else: 472 | macserial_v = "0.0.0" 473 | print("MacSerial not found!") 474 | # Print remote version if possible 475 | if self.remote and self.u.compare_versions(macserial_v, self.remote): 476 | print("Remote Version v{}".format(self.remote)) 477 | print("Current plist: {}".format(self.plist)) 478 | print("Plist type: {}".format(self.plist_type)) 479 | print("") 480 | print("1. Install/Update MacSerial") 481 | print("2. Select config.plist") 482 | print("3. Generate SMBIOS") 483 | print("4. Generate UUID") 484 | print("5. Generate ROM") 485 | print("6. List Current SMBIOS") 486 | print("7. Generate ROM With SMBIOS (Currently {})".format("Enabled" if self.gen_rom else "Disabled")) 487 | args = self.settings.get("macserial_args") 488 | if not args or not isinstance(args,basestring): args = None 489 | print("8. Additional Args (Currently: {})".format(args)) 490 | print("") 491 | print("Q. Quit") 492 | print("") 493 | menu = self.u.grab("Please select an option: ").lower() 494 | if not len(menu): 495 | return 496 | if menu == "q": 497 | self.u.custom_quit() 498 | elif menu == "1": 499 | self._get_macserial() 500 | elif menu == "2": 501 | self._get_plist() 502 | elif menu == "3": 503 | self._generate_smbios(macserial) 504 | elif menu == "4": 505 | self.u.head("Generated UUID") 506 | print("") 507 | print(str(uuid.uuid4()).upper()) 508 | print("") 509 | self.u.grab("Press [enter] to return...") 510 | elif menu == "5": 511 | self.u.head("Generated ROM") 512 | print("") 513 | print("{} ROM: {}".format("Apple" if self.rom_prefixes else "Random", self._get_rom())) 514 | print("") 515 | self.u.grab("Press [enter] to return...") 516 | elif menu == "6": 517 | self._list_current(macserial) 518 | elif menu == "7": 519 | self.gen_rom = not self.gen_rom 520 | elif menu == "8": 521 | self.get_additional_args() 522 | 523 | if __name__ == "__main__": 524 | s = Smbios() 525 | while True: 526 | try: 527 | s.main() 528 | except Exception as e: 529 | print(e) 530 | if sys.version_info >= (3, 0): 531 | input("Press [enter] to return...") 532 | else: 533 | raw_input("Press [enter] to return...") 534 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 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 | # GenSMBIOS 2 | Py script that uses acidanthera's macserial to generate SMBIOS and optionally saves them to a plist. 3 | 4 | *** 5 | 6 | ## To install: 7 | 8 | Do the following one line at a time in Terminal: 9 | 10 | git clone https://github.com/corpnewt/GenSMBIOS 11 | cd GenSMBIOS 12 | chmod +x GenSMBIOS.command 13 | 14 | Then run with either `./GenSMBIOS.command` or by double-clicking *GenSMBIOS.command* 15 | 16 | *** 17 | 18 | ## Thanks to: 19 | 20 | * acidanthera and crew for the [macserial](https://github.com/acidanthera/macserial) application 21 | -------------------------------------------------------------------------------- /Scripts/__init__.py: -------------------------------------------------------------------------------- 1 | from os.path import dirname, basename, isfile 2 | import glob 3 | modules = glob.glob(dirname(__file__)+"/*.py") 4 | __all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')] -------------------------------------------------------------------------------- /Scripts/downloader.py: -------------------------------------------------------------------------------- 1 | import sys, os, time, ssl, gzip, multiprocessing 2 | from io import BytesIO 3 | # Python-aware urllib stuff 4 | try: 5 | from urllib.request import urlopen, Request 6 | import queue as q 7 | except ImportError: 8 | # Import urllib2 to catch errors 9 | import urllib2 10 | from urllib2 import urlopen, Request 11 | import Queue as q 12 | 13 | TERMINAL_WIDTH = 120 if os.name=="nt" else 80 14 | 15 | def get_size(size, suffix=None, use_1024=False, round_to=2, strip_zeroes=False): 16 | # size is the number of bytes 17 | # suffix is the target suffix to locate (B, KB, MB, etc) - if found 18 | # use_2014 denotes whether or not we display in MiB vs MB 19 | # round_to is the number of dedimal points to round our result to (0-15) 20 | # strip_zeroes denotes whether we strip out zeroes 21 | 22 | # Failsafe in case our size is unknown 23 | if size == -1: 24 | return "Unknown" 25 | # Get our suffixes based on use_1024 26 | ext = ["B","KiB","MiB","GiB","TiB","PiB"] if use_1024 else ["B","KB","MB","GB","TB","PB"] 27 | div = 1024 if use_1024 else 1000 28 | s = float(size) 29 | s_dict = {} # Initialize our dict 30 | # Iterate the ext list, and divide by 1000 or 1024 each time to setup the dict {ext:val} 31 | for e in ext: 32 | s_dict[e] = s 33 | s /= div 34 | # Get our suffix if provided - will be set to None if not found, or if started as None 35 | suffix = next((x for x in ext if x.lower() == suffix.lower()),None) if suffix else suffix 36 | # Get the largest value that's still over 1 37 | biggest = suffix if suffix else next((x for x in ext[::-1] if s_dict[x] >= 1), "B") 38 | # Determine our rounding approach - first make sure it's an int; default to 2 on error 39 | try:round_to=int(round_to) 40 | except:round_to=2 41 | round_to = 0 if round_to < 0 else 15 if round_to > 15 else round_to # Ensure it's between 0 and 15 42 | bval = round(s_dict[biggest], round_to) 43 | # Split our number based on decimal points 44 | a,b = str(bval).split(".") 45 | # Check if we need to strip or pad zeroes 46 | b = b.rstrip("0") if strip_zeroes else b.ljust(round_to,"0") if round_to > 0 else "" 47 | return "{:,}{} {}".format(int(a),"" if not b else "."+b,biggest) 48 | 49 | def _process_hook(queue, total_size, bytes_so_far=0, update_interval=1.0, max_packets=0): 50 | packets = [] 51 | speed = remaining = "" 52 | last_update = time.time() 53 | while True: 54 | # Write our info first so we have *some* status while 55 | # waiting for packets 56 | if total_size > 0: 57 | percent = float(bytes_so_far) / total_size 58 | percent = round(percent*100, 2) 59 | t_s = get_size(total_size) 60 | try: 61 | b_s = get_size(bytes_so_far, t_s.split(" ")[1]) 62 | except: 63 | b_s = get_size(bytes_so_far) 64 | perc_str = " {:.2f}%".format(percent) 65 | bar_width = (TERMINAL_WIDTH // 3)-len(perc_str) 66 | progress = "=" * int(bar_width * (percent/100)) 67 | sys.stdout.write("\r\033[K{}/{} | {}{}{}{}{}".format( 68 | b_s, 69 | t_s, 70 | progress, 71 | " " * (bar_width-len(progress)), 72 | perc_str, 73 | speed, 74 | remaining 75 | )) 76 | else: 77 | b_s = get_size(bytes_so_far) 78 | sys.stdout.write("\r\033[K{}{}".format(b_s, speed)) 79 | sys.stdout.flush() 80 | # Now we gather the next packet 81 | try: 82 | packet = queue.get(timeout=update_interval) 83 | # Packets should be formatted as a tuple of 84 | # (timestamp, len(bytes_downloaded)) 85 | # If "DONE" is passed, we assume the download 86 | # finished - and bail 87 | if packet == "DONE": 88 | print("") # Jump to the next line 89 | return 90 | # Append our packet to the list and ensure we're not 91 | # beyond our max. 92 | # Only check max if it's > 0 93 | packets.append(packet) 94 | if max_packets > 0: 95 | packets = packets[-max_packets:] 96 | # Increment our bytes so far as well 97 | bytes_so_far += packet[1] 98 | except q.Empty: 99 | # Didn't get anything - reset the speed 100 | # and packets 101 | packets = [] 102 | speed = " | 0 B/s" 103 | remaining = " | ?? left" if total_size > 0 else "" 104 | except KeyboardInterrupt: 105 | print("") # Jump to the next line 106 | return 107 | # If we have packets and it's time for an update, process 108 | # the info. 109 | update_check = time.time() 110 | if packets and update_check - last_update >= update_interval: 111 | last_update = update_check # Refresh our update timestamp 112 | speed = " | ?? B/s" 113 | if len(packets) > 1: 114 | # Let's calculate the amount downloaded over how long 115 | try: 116 | first,last = packets[0][0],packets[-1][0] 117 | chunks = sum([float(x[1]) for x in packets]) 118 | t = last-first 119 | assert t >= 0 120 | bytes_speed = 1. / t * chunks 121 | speed = " | {}/s".format(get_size(bytes_speed,round_to=1)) 122 | # Get our remaining time 123 | if total_size > 0: 124 | seconds_left = (total_size-bytes_so_far) / bytes_speed 125 | days = seconds_left // 86400 126 | hours = (seconds_left - (days*86400)) // 3600 127 | mins = (seconds_left - (days*86400) - (hours*3600)) // 60 128 | secs = seconds_left - (days*86400) - (hours*3600) - (mins*60) 129 | if days > 99 or bytes_speed == 0: 130 | remaining = " | ?? left" 131 | else: 132 | remaining = " | {}{:02d}:{:02d}:{:02d} left".format( 133 | "{}:".format(int(days)) if days else "", 134 | int(hours), 135 | int(mins), 136 | int(round(secs)) 137 | ) 138 | except: 139 | pass 140 | # Clear the packets so we don't reuse the same ones 141 | packets = [] 142 | 143 | class Downloader: 144 | 145 | def __init__(self,**kwargs): 146 | self.ua = kwargs.get("useragent",{"User-Agent":"Mozilla"}) 147 | self.chunk = 1048576 # 1024 x 1024 i.e. 1MiB 148 | if os.name=="nt": os.system("color") # Initialize cmd for ANSI escapes 149 | # Provide reasonable default logic to workaround macOS CA file handling 150 | cafile = ssl.get_default_verify_paths().openssl_cafile 151 | try: 152 | # If default OpenSSL CA file does not exist, use that from certifi 153 | if not os.path.exists(cafile): 154 | import certifi 155 | cafile = certifi.where() 156 | self.ssl_context = ssl.create_default_context(cafile=cafile) 157 | except: 158 | # None of the above worked, disable certificate verification for now 159 | self.ssl_context = ssl._create_unverified_context() 160 | return 161 | 162 | def _decode(self, value, encoding="utf-8", errors="ignore"): 163 | # Helper method to only decode if bytes type 164 | if sys.version_info >= (3,0) and isinstance(value, bytes): 165 | return value.decode(encoding,errors) 166 | return value 167 | 168 | def _update_main_name(self): 169 | # Windows running python 2 seems to have issues with multiprocessing 170 | # if the case of the main script's name is incorrect: 171 | # e.g. Downloader.py vs downloader.py 172 | # 173 | # To work around this, we try to scrape for the correct case if 174 | # possible. 175 | try: 176 | path = os.path.abspath(sys.modules["__main__"].__file__) 177 | except AttributeError as e: 178 | # This likely means we're running from the interpreter 179 | # directly 180 | return None 181 | if not os.path.isfile(path): 182 | return None 183 | # Get the file name and folder path 184 | name = os.path.basename(path).lower() 185 | fldr = os.path.dirname(path) 186 | # Walk the files in the folder until we find our 187 | # name - then steal its case and update that path 188 | for f in os.listdir(fldr): 189 | if f.lower() == name: 190 | # Got it 191 | new_path = os.path.join(fldr,f) 192 | sys.modules["__main__"].__file__ = new_path 193 | return new_path 194 | # If we got here, it wasn't found 195 | return None 196 | 197 | def _get_headers(self, headers = None): 198 | # Fall back on the default ua if none provided 199 | target = headers if isinstance(headers,dict) else self.ua 200 | new_headers = {} 201 | # Shallow copy to prevent changes to the headers 202 | # overriding the original 203 | for k in target: 204 | new_headers[k] = target[k] 205 | return new_headers 206 | 207 | def open_url(self, url, headers = None): 208 | headers = self._get_headers(headers) 209 | # Wrap up the try/except block so we don't have to do this for each function 210 | try: 211 | response = urlopen(Request(url, headers=headers), context=self.ssl_context) 212 | except Exception as e: 213 | # No fixing this - bail 214 | return None 215 | return response 216 | 217 | def get_size(self, *args, **kwargs): 218 | return get_size(*args,**kwargs) 219 | 220 | def get_string(self, url, progress = True, headers = None, expand_gzip = True): 221 | response = self.get_bytes(url,progress,headers,expand_gzip) 222 | if response is None: return None 223 | return self._decode(response) 224 | 225 | def get_bytes(self, url, progress = True, headers = None, expand_gzip = True): 226 | response = self.open_url(url, headers) 227 | if response is None: return None 228 | try: total_size = int(response.headers['Content-Length']) 229 | except: total_size = -1 230 | chunk_so_far = b"" 231 | packets = queue = process = None 232 | if progress: 233 | # Make sure our vars are initialized 234 | packets = [] if progress else None 235 | queue = multiprocessing.Queue() 236 | # Create the multiprocess and start it 237 | process = multiprocessing.Process( 238 | target=_process_hook, 239 | args=(queue,total_size) 240 | ) 241 | process.daemon = True 242 | # Filthy hack for earlier python versions on Windows 243 | if os.name == "nt" and hasattr(multiprocessing,"forking"): 244 | self._update_main_name() 245 | process.start() 246 | try: 247 | while True: 248 | chunk = response.read(self.chunk) 249 | if progress: 250 | # Add our items to the queue 251 | queue.put((time.time(),len(chunk))) 252 | if not chunk: break 253 | chunk_so_far += chunk 254 | finally: 255 | # Close the response whenever we're done 256 | response.close() 257 | if expand_gzip and response.headers.get("Content-Encoding","unknown").lower() == "gzip": 258 | fileobj = BytesIO(chunk_so_far) 259 | gfile = gzip.GzipFile(fileobj=fileobj) 260 | return gfile.read() 261 | if progress: 262 | # Finalize the queue and wait 263 | queue.put("DONE") 264 | process.join() 265 | return chunk_so_far 266 | 267 | def stream_to_file(self, url, file_path, progress = True, headers = None, ensure_size_if_present = True, allow_resume = False): 268 | response = self.open_url(url, headers) 269 | if response is None: return None 270 | bytes_so_far = 0 271 | try: total_size = int(response.headers['Content-Length']) 272 | except: total_size = -1 273 | packets = queue = process = None 274 | mode = "wb" 275 | if allow_resume and os.path.isfile(file_path) and total_size != -1: 276 | # File exists, we're resuming and have a target size. Check the 277 | # local file size. 278 | current_size = os.stat(file_path).st_size 279 | if current_size == total_size: 280 | # File is already complete - return the path 281 | return file_path 282 | elif current_size < total_size: 283 | response.close() 284 | # File is not complete - seek to our current size 285 | bytes_so_far = current_size 286 | mode = "ab" # Append 287 | # We also need to try creating a new request 288 | # in order to pass our range header 289 | new_headers = self._get_headers(headers) 290 | # Get the start byte, 0-indexed 291 | byte_string = "bytes={}-".format(current_size) 292 | new_headers["Range"] = byte_string 293 | response = self.open_url(url, new_headers) 294 | if response is None: return None 295 | if progress: 296 | # Make sure our vars are initialized 297 | packets = [] if progress else None 298 | queue = multiprocessing.Queue() 299 | # Create the multiprocess and start it 300 | process = multiprocessing.Process( 301 | target=_process_hook, 302 | args=(queue,total_size,bytes_so_far) 303 | ) 304 | process.daemon = True 305 | # Filthy hack for earlier python versions on Windows 306 | if os.name == "nt" and hasattr(multiprocessing,"forking"): 307 | self._update_main_name() 308 | process.start() 309 | with open(file_path,mode) as f: 310 | try: 311 | while True: 312 | chunk = response.read(self.chunk) 313 | bytes_so_far += len(chunk) 314 | if progress: 315 | # Add our items to the queue 316 | queue.put((time.time(),len(chunk))) 317 | if not chunk: break 318 | f.write(chunk) 319 | finally: 320 | # Close the response whenever we're done 321 | response.close() 322 | if progress: 323 | # Finalize the queue and wait 324 | queue.put("DONE") 325 | process.join() 326 | if ensure_size_if_present and total_size != -1: 327 | # We're verifying size - make sure we got what we asked for 328 | if bytes_so_far != total_size: 329 | return None # We didn't - imply it failed 330 | return file_path if os.path.exists(file_path) else None 331 | -------------------------------------------------------------------------------- /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/prefix.json: -------------------------------------------------------------------------------- 1 | [ 2 | "000393", 3 | "000A27", 4 | "000A95", 5 | "000D93", 6 | "0010FA", 7 | "001124", 8 | "001451", 9 | "0016CB", 10 | "0017F2", 11 | "0019E3", 12 | "001B63", 13 | "001CB3", 14 | "001D4F", 15 | "001E52", 16 | "001EC2", 17 | "001F5B", 18 | "001FF3", 19 | "0021E9", 20 | "002241", 21 | "002312", 22 | "002332", 23 | "00236C", 24 | "0023DF", 25 | "002436", 26 | "002500", 27 | "00254B", 28 | "0025BC", 29 | "002608", 30 | "00264A", 31 | "0026B0", 32 | "0026BB", 33 | "003065", 34 | "003EE1", 35 | "0050E4", 36 | "0056CD", 37 | "006171", 38 | "006D52", 39 | "008865", 40 | "00B362", 41 | "00C610", 42 | "00CDFE", 43 | "00F4B9", 44 | "00F76F", 45 | "040CCE", 46 | "041552", 47 | "041E64", 48 | "042665", 49 | "04489A", 50 | "044BED", 51 | "0452F3", 52 | "045453", 53 | "0469F8", 54 | "04D3CF", 55 | "04DB56", 56 | "04E536", 57 | "04F13E", 58 | "04F7E4", 59 | "086698", 60 | "086D41", 61 | "087045", 62 | "087402", 63 | "0C1539", 64 | "0C3021", 65 | "0C3E9F", 66 | "0C4DE9", 67 | "0C5101", 68 | "0C74C2", 69 | "0C771A", 70 | "0CBC9F", 71 | "0CD746", 72 | "101C0C", 73 | "1040F3", 74 | "10417F", 75 | "1093E9", 76 | "109ADD", 77 | "10DDB1", 78 | "14109F", 79 | "145A05", 80 | "148FC6", 81 | "1499E2", 82 | "14BD61", 83 | "182032", 84 | "183451", 85 | "186590", 86 | "189EFC", 87 | "18AF61", 88 | "18AF8F", 89 | "18E7F4", 90 | "18EE69", 91 | "18F643", 92 | "1C1AC0", 93 | "1C5CF2", 94 | "1C9148", 95 | "1C9E46", 96 | "1CABA7", 97 | "1CE62B", 98 | "203CAE", 99 | "20768F", 100 | "2078F0", 101 | "207D74", 102 | "209BCD", 103 | "20A2E4", 104 | "20AB37", 105 | "20C9D0", 106 | "241EEB", 107 | "24240E", 108 | "245BA7", 109 | "24A074", 110 | "24A2E1", 111 | "24AB81", 112 | "24E314", 113 | "24F094", 114 | "280B5C", 115 | "283737", 116 | "285AEB", 117 | "286AB8", 118 | "286ABA", 119 | "28A02B", 120 | "28CFDA", 121 | "28CFE9", 122 | "28E02C", 123 | "28E14C", 124 | "28E7CF", 125 | "28ED6A", 126 | "28F076", 127 | "2C1F23", 128 | "2C200B", 129 | "2C3361", 130 | "2CB43A", 131 | "2CBE08", 132 | "2CF0A2", 133 | "2CF0EE", 134 | "3010E4", 135 | "30636B", 136 | "3090AB", 137 | "30F7C5", 138 | "341298", 139 | "34159E", 140 | "34363B", 141 | "3451C9", 142 | "34A395", 143 | "34AB37", 144 | "34C059", 145 | "34E2FD", 146 | "380F4A", 147 | "38484C", 148 | "3871DE", 149 | "38B54D", 150 | "38C986", 151 | "38CADA", 152 | "3C0754", 153 | "3C15C2", 154 | "3CAB8E", 155 | "3CD0F8", 156 | "3CE072", 157 | "403004", 158 | "40331A", 159 | "403CFC", 160 | "404D7F", 161 | "406C8F", 162 | "40A6D9", 163 | "40B395", 164 | "40D32D", 165 | "440010", 166 | "442A60", 167 | "444C0C", 168 | "44D884", 169 | "44FB42", 170 | "483B38", 171 | "48437C", 172 | "484BAA", 173 | "4860BC", 174 | "48746E", 175 | "48A195", 176 | "48BF6B", 177 | "48D705", 178 | "48E9F1", 179 | "4C3275", 180 | "4C57CA", 181 | "4C74BF", 182 | "4C7C5F", 183 | "4C8D79", 184 | "4CB199", 185 | "503237", 186 | "507A55", 187 | "5082D5", 188 | "50EAD6", 189 | "542696", 190 | "544E90", 191 | "54724F", 192 | "549F13", 193 | "54AE27", 194 | "54E43A", 195 | "54EAA8", 196 | "581FAA", 197 | "58404E", 198 | "5855CA", 199 | "587F57", 200 | "58B035", 201 | "5C5948", 202 | "5C8D4E", 203 | "5C95AE", 204 | "5C969D", 205 | "5C97F3", 206 | "5CADCF", 207 | "5CF5DA", 208 | "5CF7E6", 209 | "5CF938", 210 | "600308", 211 | "60334B", 212 | "606944", 213 | "609217", 214 | "609AC1", 215 | "60A37D", 216 | "60C547", 217 | "60D9C7", 218 | "60F445", 219 | "60F81D", 220 | "60FACD", 221 | "60FB42", 222 | "60FEC5", 223 | "64200C", 224 | "6476BA", 225 | "649ABE", 226 | "64A3CB", 227 | "64A5C3", 228 | "64B0A6", 229 | "64B9E8", 230 | "64E682", 231 | "680927", 232 | "685B35", 233 | "68644B", 234 | "68967B", 235 | "689C70", 236 | "68A86D", 237 | "68AE20", 238 | "68D93C", 239 | "68DBCA", 240 | "68FB7E", 241 | "6C19C0", 242 | "6C3E6D", 243 | "6C4008", 244 | "6C709F", 245 | "6C72E7", 246 | "6C8DC1", 247 | "6C94F8", 248 | "6CAB31", 249 | "6CC26B", 250 | "701124", 251 | "7014A6", 252 | "703EAC", 253 | "70480F", 254 | "705681", 255 | "70700D", 256 | "7073CB", 257 | "7081EB", 258 | "70A2B3", 259 | "70CD60", 260 | "70DEE2", 261 | "70E72C", 262 | "70ECE4", 263 | "70F087", 264 | "741BB2", 265 | "748114", 266 | "748D08", 267 | "74E1B6", 268 | "74E2F5", 269 | "7831C1", 270 | "783A84", 271 | "784F43", 272 | "786C1C", 273 | "787E61", 274 | "789F70", 275 | "78A3E4", 276 | "78CA39", 277 | "78D75F", 278 | "78FD94", 279 | "7C0191", 280 | "7C04D0", 281 | "7C11BE", 282 | "7C5049", 283 | "7C6D62", 284 | "7C6DF8", 285 | "7CC3A1", 286 | "7CC537", 287 | "7CD1C3", 288 | "7CF05F", 289 | "7CFADF", 290 | "80006E", 291 | "804971", 292 | "80929F", 293 | "80BE05", 294 | "80D605", 295 | "80E650", 296 | "80EA96", 297 | "80ED2C", 298 | "842999", 299 | "843835", 300 | "84788B", 301 | "848506", 302 | "8489AD", 303 | "848E0C", 304 | "84A134", 305 | "84B153", 306 | "84FCAC", 307 | "84FCFE", 308 | "881FA1", 309 | "885395", 310 | "8863DF", 311 | "8866A5", 312 | "886B6E", 313 | "88C663", 314 | "88CB87", 315 | "88E87F", 316 | "8C006D", 317 | "8C2937", 318 | "8C2DAA", 319 | "8C5877", 320 | "8C7B9D", 321 | "8C7C92", 322 | "8C8EF2", 323 | "8C8FE9", 324 | "8CFABA", 325 | "9027E4", 326 | "903C92", 327 | "9060F1", 328 | "907240", 329 | "90840D", 330 | "908D6C", 331 | "90B0ED", 332 | "90B21F", 333 | "90B931", 334 | "90C1C6", 335 | "90FD61", 336 | "949426", 337 | "94E96A", 338 | "94F6A3", 339 | "9801A7", 340 | "9803D8", 341 | "9810E8", 342 | "985AEB", 343 | "989E63", 344 | "98B8E3", 345 | "98D6BB", 346 | "98E0D9", 347 | "98F0AB", 348 | "98FE94", 349 | "9C04EB", 350 | "9C207B", 351 | "9C293F", 352 | "9C35EB", 353 | "9C4FDA", 354 | "9C84BF", 355 | "9C8BA0", 356 | "9CF387", 357 | "9CF48E", 358 | "9CFC01", 359 | "A01828", 360 | "A03BE3", 361 | "A0999B", 362 | "A0D795", 363 | "A0EDCD", 364 | "A43135", 365 | "A45E60", 366 | "A46706", 367 | "A4B197", 368 | "A4B805", 369 | "A4C361", 370 | "A4D18C", 371 | "A4D1D2", 372 | "A4F1E8", 373 | "A82066", 374 | "A85B78", 375 | "A860B6", 376 | "A8667F", 377 | "A886DD", 378 | "A88808", 379 | "A88E24", 380 | "A8968A", 381 | "A8BBCF", 382 | "A8FAD8", 383 | "AC293A", 384 | "AC3C0B", 385 | "AC61EA", 386 | "AC7F3E", 387 | "AC87A3", 388 | "ACBC32", 389 | "ACCF5C", 390 | "ACFDEC", 391 | "B03495", 392 | "B0481A", 393 | "B065BD", 394 | "B0702D", 395 | "B09FBA", 396 | "B418D1", 397 | "B44BD2", 398 | "B48B19", 399 | "B49CDF", 400 | "B4F0AB", 401 | "B8098A", 402 | "B817C2", 403 | "B844D9", 404 | "B853AC", 405 | "B8782E", 406 | "B88D12", 407 | "B8C75D", 408 | "B8E856", 409 | "B8F6B1", 410 | "B8FF61", 411 | "BC3BAF", 412 | "BC4CC4", 413 | "BC52B7", 414 | "BC5436", 415 | "BC6778", 416 | "BC6C21", 417 | "BC926B", 418 | "BC9FEF", 419 | "BCA920", 420 | "BCEC5D", 421 | "C01ADA", 422 | "C06394", 423 | "C0847A", 424 | "C09F42", 425 | "C0CCF8", 426 | "C0CECD", 427 | "C0D012", 428 | "C0F2FB", 429 | "C42C03", 430 | "C4B301", 431 | "C81EE7", 432 | "C82A14", 433 | "C8334B", 434 | "C869CD", 435 | "C86F1D", 436 | "C88550", 437 | "C8B5B7", 438 | "C8BCC8", 439 | "C8E0EB", 440 | "C8F650", 441 | "CC088D", 442 | "CC08E0", 443 | "CC20E8", 444 | "CC25EF", 445 | "CC29F5", 446 | "CC4463", 447 | "CC785F", 448 | "CCC760", 449 | "D0034B", 450 | "D023DB", 451 | "D02598", 452 | "D03311", 453 | "D04F7E", 454 | "D0A637", 455 | "D0C5F3", 456 | "D0E140", 457 | "D4619D", 458 | "D49A20", 459 | "D4DCCD", 460 | "D4F46F", 461 | "D8004D", 462 | "D81D72", 463 | "D83062", 464 | "D89695", 465 | "D89E3F", 466 | "D8A25E", 467 | "D8BB2C", 468 | "D8CF9C", 469 | "D8D1CB", 470 | "DC0C5C", 471 | "DC2B2A", 472 | "DC2B61", 473 | "DC3714", 474 | "DC415F", 475 | "DC86D8", 476 | "DC9B9C", 477 | "DCA4CA", 478 | "DCA904", 479 | "E05F45", 480 | "E06678", 481 | "E0ACCB", 482 | "E0B52D", 483 | "E0B9BA", 484 | "E0C767", 485 | "E0C97A", 486 | "E0F5C6", 487 | "E0F847", 488 | "E425E7", 489 | "E48B7F", 490 | "E498D6", 491 | "E49A79", 492 | "E4C63D", 493 | "E4CE8F", 494 | "E4E4AB", 495 | "E8040B", 496 | "E80688", 497 | "E8802E", 498 | "E88D28", 499 | "E8B2AC", 500 | "EC3586", 501 | "EC852F", 502 | "ECADB8", 503 | "F02475", 504 | "F07960", 505 | "F099BF", 506 | "F0B0E7", 507 | "F0B479", 508 | "F0C1F1", 509 | "F0CBA1", 510 | "F0D1A9", 511 | "F0DBE2", 512 | "F0DBF8", 513 | "F0DCE2", 514 | "F0F61C", 515 | "F40F24", 516 | "F41BA1", 517 | "F431C3", 518 | "F437B7", 519 | "F45C89", 520 | "F4F15A", 521 | "F4F951", 522 | "F80377", 523 | "F81EDF", 524 | "F82793", 525 | "F86214", 526 | "FC253F", 527 | "FCD848", 528 | "FCE998", 529 | "FCFC48" 530 | ] -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------