[
  {
    "path": ".gitattributes",
    "content": "# Recommedations from GitHub help pages\n# Set the default behavior, in case people don't have core.autocrlf set.\n* text=auto\n\n# Declare files that will always have CRLF line endings on checkout.\n*.cmd text eol=crlf\n\n# Declare files that will always have LF line endings on checkout.\n*.sh text eol=lf\n*.py text eol=lf\n\n# Declare files that are binary on checkout.\n*.exe binary"
  },
  {
    "path": ".github/workflows/main.yml",
    "content": "# This is a basic workflow to help you get started with Actions\n\nname: CI\n\n# Controls when the action will run. Triggers the workflow on push or pull request\n# events but only for the master branch\non:\n  push:\n#     branches: [ master ]\n    tags:\n      - '[0-9].[0-9]+.[0-9]+'\n  pull_request:\n    branches: [ master ]\n    tags:\n    - '[0-9].[0-9]+.[0-9]+'\n\n# A workflow run is made up of one or more jobs that can run sequentially or in parallel\njobs:\n  # This workflow contains a single job called \"build\"\n  build:\n    # The type of runner that the job will run on\n    runs-on: ubuntu-latest\n\n    # Steps represent a sequence of tasks that will be executed as part of the job\n    steps:\n      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it\n      - uses: actions/checkout@v2\n      - name: Set env\n        run: echo \"RELEASE_VERSION=${GITHUB_REF/refs\\/tags\\//}\" >> $GITHUB_ENV\n\n      # Runs a few command using the runners shell\n      - name: Run a multi-line script\n        run: | \n          # pwd debug\n          tar -cvzf unlocker.tgz etc\n          tar -cvzf esxi-unlocker-${{ env.RELEASE_VERSION }}.tgz *.*\n          # ls -al more debug\n\n      # release automation.gz\n      - name: Create Release\n        id: create_release\n        uses: actions/create-release@v1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token\n        with:\n          tag_name: ${{ github.ref }}.${{ github.run_number }}\n          release_name: Release ${{ env.RELEASE_VERSION }}\n          draft: true\n          prerelease: true\n      - name: Upload Release Asset\n        id: upload-release-asset\n        uses: actions/upload-release-asset@v1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          upload_url: ${{ steps.create_release.outputs.upload_url }}\n          asset_path: ./readme.md\n          asset_name: readme.md\n          asset_content_type: text/markdown \n      - name: Upload Release Asset2\n        id: upload-release-asset2\n        uses: actions/upload-release-asset@v1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          upload_url: ${{ steps.create_release.outputs.upload_url }}\n          asset_path: ./esxi-unlocker-${{ env.RELEASE_VERSION }}.tgz\n          asset_name: esxi-unlocker-${{ env.RELEASE_VERSION }}.tgz\n          asset_content_type: application/zip \n"
  },
  {
    "path": ".github/workflows/manual.yml",
    "content": "# This is a basic workflow to help you get started with Actions\n\nname: Manual CI\n\n# Controls when the action will run. Triggers the workflow on push or pull request\n# events but only for the master branch\n\non:\n  workflow_dispatch:\n    inputs:\n      semver:\n        description: 'Version'\n        required: true\n\n# A workflow run is made up of one or more jobs that can run sequentially or in parallel\njobs:\n  # This workflow contains a single job called \"build\"\n  build:\n    # The type of runner that the job will run on\n    runs-on: ubuntu-latest\n\n    # Steps represent a sequence of tasks that will be executed as part of the job\n    steps:\n      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it\n      - uses: actions/checkout@v2\n\n      # Runs a few command using the runners shell\n      - name: Run a multi-line script\n        run: | \n          # pwd debug\n          tar -cvzf unlocker.tgz etc\n          tar -cvzf esxi-unlocker-${{ github.event.inputs.semver }}.tgz *.*\n          # ls -al more debug\n\n      # release automation.gz\n      - name: Create Release\n        id: create_release\n        uses: actions/create-release@v1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token\n        with:\n          tag_name: ${{ github.event.inputs.semver }}\n          release_name: Release ${{ github.event.inputs.semver }}\n#           body: |\n#             Changes in this Release\n#             - First Change\n#             - Second Change\n          draft: true\n          prerelease: true\n      - name: Upload Release Asset\n        id: upload-release-asset\n        uses: actions/upload-release-asset@v1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          upload_url: ${{ steps.create_release.outputs.upload_url }}\n          asset_path: ./readme.md\n          asset_name: readme.md\n          asset_content_type: text/plain \n      - name: Upload Release Asset2\n        id: upload-release-asset2\n        uses: actions/upload-release-asset@v1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          upload_url: ${{ steps.create_release.outputs.upload_url }}\n          asset_path: ./esxi-unlocker-${{ github.event.inputs.semver }}.tgz\n          asset_name: esxi-unlocker-${{ github.event.inputs.semver }}.tgz\n          asset_content_type: application/zip \n"
  },
  {
    "path": ".gitignore",
    "content": "backup/\ntools/\n.idea/\nsamples/\ntests/\nbuild/\ndist/\n*.spec\n*.pyc\n*.tar\n*.tgz\n*.vmtar\n*.vgz\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018 David Parsons\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "esxi-build.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nThe MIT License (MIT)\n\nCopyright (c) 2014-2018 Dave Parsons & Sam Bingner\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the 'Software'), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\"\"\"\n\nimport datetime\nimport subprocess\nimport sys\n\n\nif sys.version_info < (2, 7):\n    sys.stderr.write('You need Python 2.7 or later\\n')\n    sys.exit(1)\n\n# TODO: Change for a new release\nVERSION = '3.0.2'\nFILEVER = '302'\nFILENAME = 'esxi-unlocker-302.tgz'\n\nTIMESTAMP = '{:%Y%m%d%H%M.%S}'.format(datetime.datetime.now())\nTOUCH = 'touch -t ' + TIMESTAMP\nGTARUNLOCKER = '/usr/local/bin/gtar czvf unlocker.tgz etc'\nGTARDISTRIB = '/usr/local/bin/gtar czvf ' + FILENAME + \\\n              ' unlocker.tgz esxi-install.sh esxi-uninstall.sh esxi-smctest.sh readme.txt'\n\n\ndef main():\n\n    # Timestamp files for release\n    print('\\nTimestamping files...')\n    subprocess.call(TOUCH + ' readme.txt', shell=True)\n    subprocess.call(TOUCH + ' esxi-install.sh', shell=True)\n    subprocess.call(TOUCH + ' esxi-uninstall.sh', shell=True)\n    subprocess.call(TOUCH + ' esxi-smctest.sh', shell=True)\n    subprocess.call(TOUCH + ' etc', shell=True)\n    subprocess.call(TOUCH + ' etc/rc.local.d', shell=True)\n    subprocess.call(TOUCH + ' etc/rc.local.d/unlocker.py', shell=True)\n\n    # Build the gzipped tar file unlocker.tgz\n    print('\\nCreating unlocker.tgz...')\n    subprocess.call(GTARUNLOCKER, shell=True)\n    subprocess.call(TOUCH + ' unlocker.tgz', shell=True)\n\n    # Build the distribution file esxi-unlocker-VER.tgz\n    print('\\nCreating ' + FILENAME + '...')\n    subprocess.call(GTARDISTRIB, shell=True)\n    subprocess.call(TOUCH + ' ' + FILENAME, shell=True)\n\n\nif __name__ == '__main__':\n    if sys.platform == 'darwin':\n        print('ESXi-Build for macOS')\n        main()\n    else:\n        print('ESXi-Build only supported on macOS')\n"
  },
  {
    "path": "esxi-install.sh",
    "content": "#!/bin/sh\nset -e\n#set -x\n\necho VMware Unlocker 3.0.2\necho ===============================\necho Copyright: Dave Parsons 2011-18\n\n# Ensure we only use unmodified commands\nexport PATH=/bin:/sbin:/usr/bin:/usr/sbin\n\necho Installing unlocker.tgz\nBootModuleConfig.sh --verbose --add=unlocker.tgz\necho Success - please now restart the server!\n"
  },
  {
    "path": "esxi-smctest.sh",
    "content": "#!/bin/sh\ngrep -il \\(c\\)AppleComputerInc /bin/vmx*\nvim-cmd hostsvc/hosthardware | grep smcPresent | cut -d ',' -f 1 | sed 's/^[ \\t]*//'\nesxcli system visorfs tardisk list | grep custom.vgz\n"
  },
  {
    "path": "esxi-uninstall.sh",
    "content": "#!/bin/sh\nset -e\n#set -x\n\necho VMware Unlocker 3.0.2\necho ===============================\necho Copyright: Dave Parsons 2011-18\n\n# Ensure we only use unmodified commands\nexport PATH=/bin:/sbin:/usr/bin:/usr/sbin\n\necho Uninstalling unlocker.tgz\nBootModuleConfig.sh --verbose --remove=unlocker.tgz\necho Success - please now restart the server!\n"
  },
  {
    "path": "etc/rc.local.d/unlocker.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nThe MIT License (MIT)\n\nCopyright (c) 2014-2018 Dave Parsons & Sam Bingner\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the 'Software'), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\nvSMC Header Structure\nOffset  Length  Struct Type Description\n----------------------------------------\n0x00/00 0x08/08 Q      ptr  Offset to key table\n0x08/08 0x04/4  I      int  Number of private keys\n0x0C/12 0x04/4  I      int  Number of public keys\n\nvSMC Key Data Structure\nOffset  Length  Struct Type Description\n----------------------------------------\n0x00/00 0x04/04 4s     int  Key name (byte reversed e.g. #KEY is YEK#)\n0x04/04 0x01/01 B      byte Length of returned data\n0x05/05 0x04/04 4s     int  Data type (byte reversed e.g. ui32 is 23iu)\n0x09/09 0x01/01 B      byte Flag R/W\n0x0A/10 0x06/06 6x     byte Padding\n0x10/16 0x08/08 Q      ptr  Internal VMware routine\n0x18/24 0x30/48 48B    byte Data\n\"\"\"\n\nimport codecs\nimport os\nimport shutil\nimport struct\nimport subprocess\nimport sys\n\nif sys.version_info < (2, 7):\n    sys.stderr.write('You need Python 2.7 or later\\n')\n    sys.exit(1)\n\n\ndef bytetohex(data):\n    if sys.version_info > (3, 0):\n        # Python 3 code in this block\n        return \"\".join(\"{:02X} \".format(c) for c in data)\n    else:\n        # Python 2 code in this block\n        return \"\".join(\"{:02X} \".format(ord(c)) for c in data)\n\n\ndef joinpath(folder, filename):\n    return os.path.join(folder, filename)\n\n\ndef printkey(i, offset, smc_key, smc_data):\n    print(str(i + 1).zfill(3)\n          + ' ' + hex(offset)\n          + ' ' + smc_key[0][::-1].decode('UTF-8')\n          + ' ' + str(smc_key[1]).zfill(2)\n          + ' ' + smc_key[2][::-1].replace(b'\\x00', b' ').decode('UTF-8')\n          + ' ' + '{0:#0{1}x}'.format(smc_key[3], 4)\n          + ' ' + hex(smc_key[4])\n          + ' ' + bytetohex(smc_data))\n\n\ndef set_bit(value, bit):\n    return value | (1 << bit)\n\n\ndef clear_bit(value, bit):\n    return value & ~(1 << bit)\n\n\ndef test_bit(value, bit):\n    return value & bit\n\n\nE_CLASS64 = 2\nE_SHT_RELA = 4\n\n\ndef patchelf(f, oldoffset, newoffset):\n    f.seek(0)\n    magic = f.read(4)\n    if not magic == b'\\x7fELF':\n        raise Exception('Magic number does not match')\n\n    ei_class = struct.unpack('=B', f.read(1))[0]\n    if ei_class != E_CLASS64:\n        raise Exception('Not 64bit elf header: ' + ei_class)\n\n    f.seek(40)\n    e_shoff = struct.unpack('=Q', f.read(8))[0]\n    f.seek(58)\n    e_shentsize = struct.unpack('=H', f.read(2))[0]\n    e_shnum = struct.unpack('=H', f.read(2))[0]\n    e_shstrndx = struct.unpack('=H', f.read(2))[0]\n\n    print('e_shoff: 0x{:x} e_shentsize: 0x{:x} e_shnum:0x{:x} e_shstrndx:0x{:x}'.format(e_shoff, e_shentsize,\n                                                                                        e_shnum, e_shstrndx))\n\n    for i in range(0, e_shnum):\n        f.seek(e_shoff + i * e_shentsize)\n        e_sh = struct.unpack('=LLQQQQLLQQ', f.read(e_shentsize))\n        # The name is not used.\n        # e_sh_name = e_sh[0]\n        e_sh_type = e_sh[1]\n        e_sh_offset = e_sh[4]\n        e_sh_size = e_sh[5]\n        e_sh_entsize = e_sh[9]\n        if e_sh_type == E_SHT_RELA:\n            e_sh_nument = int(e_sh_size / e_sh_entsize)\n            # print 'RELA at 0x{:x} with {:d} entries'.format(e_sh_offset, e_sh_nument)\n            for j in range(0, e_sh_nument):\n                f.seek(e_sh_offset + e_sh_entsize * j)\n                rela = struct.unpack('=QQq', f.read(e_sh_entsize))\n                r_offset = rela[0]\n                r_info = rela[1]\n                r_addend = rela[2]\n                if r_addend == oldoffset:\n                    r_addend = newoffset\n                    f.seek(e_sh_offset + e_sh_entsize * j)\n                    f.write(struct.pack('=QQq', r_offset, r_info, r_addend))\n                    print('Relocation modified at: ' + hex(e_sh_offset + e_sh_entsize * j))\n\n\ndef patchkeys(f, key):\n    # Setup struct pack string\n    key_pack = '=4sB4sB6xQ'\n    # smc_old_memptr = 0\n    smc_new_memptr = 0\n\n    # Do Until OSK1 read\n    i = 0\n    while True:\n\n        # Read key into struct str and data byte str\n        offset = key + (i * 72)\n        f.seek(offset)\n        smc_key = struct.unpack(key_pack, f.read(24))\n        smc_data = f.read(smc_key[1])\n\n        # Reset pointer to beginning of key entry\n        f.seek(offset)\n\n        if smc_key[0] == b'SKL+':\n            # Use the +LKS data routine for OSK0/1\n            smc_new_memptr = smc_key[4]\n            print('+LKS Key: ')\n            printkey(i, offset, smc_key, smc_data)\n\n        elif smc_key[0] == b'0KSO':\n            # Write new data routine pointer from +LKS\n            print('OSK0 Key Before:')\n            printkey(i, offset, smc_key, smc_data)\n            # smc_old_memptr = smc_key[4]\n            f.seek(offset)\n            f.write(struct.pack(key_pack, smc_key[0], smc_key[1], smc_key[2], smc_key[3], smc_new_memptr))\n            f.flush()\n\n            # Write new data for key\n            f.seek(offset + 24)\n            smc_new_data = codecs.encode('bheuneqjbexolgurfrjbeqfthneqrqcy', 'rot_13')\n            f.write(smc_new_data.encode('UTF-8'))\n            f.flush()\n\n            # Re-read and print key\n            f.seek(offset)\n            smc_key = struct.unpack(key_pack, f.read(24))\n            smc_data = f.read(smc_key[1])\n            print('OSK0 Key After:')\n            printkey(i, offset, smc_key, smc_data)\n\n        elif smc_key[0] == b'1KSO':\n            # Write new data routine pointer from +LKS\n            print('OSK1 Key Before:')\n            printkey(i, offset, smc_key, smc_data)\n            smc_old_memptr = smc_key[4]\n            f.seek(offset)\n            f.write(struct.pack(key_pack, smc_key[0], smc_key[1], smc_key[2], smc_key[3], smc_new_memptr))\n            f.flush()\n\n            # Write new data for key\n            f.seek(offset + 24)\n            smc_new_data = codecs.encode('rnfrqbagfgrny(p)NccyrPbzchgreVap', 'rot_13')\n            f.write(smc_new_data.encode('UTF-8'))\n            f.flush()\n\n            # Re-read and print key\n            f.seek(offset)\n            smc_key = struct.unpack(key_pack, f.read(24))\n            smc_data = f.read(smc_key[1])\n            print('OSK1 Key After:')\n            printkey(i, offset, smc_key, smc_data)\n\n            # Finished so get out of loop\n            break\n\n        else:\n            pass\n\n        i += 1\n    return smc_old_memptr, smc_new_memptr\n\n\ndef patchsmc(name, sharedobj):\n    with open(name, 'r+b') as f:\n\n        smc_old_memptr = 0\n        smc_new_memptr = 0\n\n        # Read file into string variable\n        vmx = f.read()\n\n        print('File: ' + name + '\\n')\n\n        # Setup hex string for vSMC headers\n        # These are the private and public key counts\n        smc_header_v0 = b'\\xF2\\x00\\x00\\x00\\xF0\\x00\\x00\\x00'\n        smc_header_v1 = b'\\xB4\\x01\\x00\\x00\\xB0\\x01\\x00\\x00'\n\n        # Setup hex string for #KEY key\n        key_key = b'\\x59\\x45\\x4B\\x23\\x04\\x32\\x33\\x69\\x75'\n\n        # Setup hex string for $Adr key\n        adr_key = b'\\x72\\x64\\x41\\x24\\x04\\x32\\x33\\x69\\x75'\n\n        # Find the vSMC headers\n        smc_header_v0_offset = vmx.find(smc_header_v0) - 8\n        smc_header_v1_offset = vmx.find(smc_header_v1) - 8\n\n        # Find '#KEY' keys\n        smc_key0 = vmx.find(key_key)\n        smc_key1 = vmx.rfind(key_key)\n\n        # Find '$Adr' key only V1 table\n        smc_adr = vmx.find(adr_key)\n\n        # Print vSMC0 tables and keys\n        print('appleSMCTableV0 (smc.version = \"0\")')\n        print('appleSMCTableV0 Address      : ' + hex(smc_header_v0_offset))\n        print('appleSMCTableV0 Private Key #: 0xF2/242')\n        print('appleSMCTableV0 Public Key  #: 0xF0/240')\n\n        if (smc_adr - smc_key0) != 72:\n            print('appleSMCTableV0 Table        : ' + hex(smc_key0))\n            smc_old_memptr, smc_new_memptr = patchkeys(f, smc_key0)\n        elif (smc_adr - smc_key1) != 72:\n            print('appleSMCTableV0 Table        : ' + hex(smc_key1))\n            smc_old_memptr, smc_new_memptr = patchkeys(f, smc_key1)\n\n        print()\n\n        # Print vSMC1 tables and keys\n        print('appleSMCTableV1 (smc.version = \"1\")')\n        print('appleSMCTableV1 Address      : ' + hex(smc_header_v1_offset))\n        print('appleSMCTableV1 Private Key #: 0x01B4/436')\n        print('appleSMCTableV1 Public Key  #: 0x01B0/432')\n\n        if (smc_adr - smc_key0) == 72:\n            print('appleSMCTableV1 Table        : ' + hex(smc_key0))\n            smc_old_memptr, smc_new_memptr = patchkeys(f, smc_key0)\n        elif (smc_adr - smc_key1) == 72:\n            print('appleSMCTableV1 Table        : ' + hex(smc_key1))\n            smc_old_memptr, smc_new_memptr = patchkeys(f, smc_key1)\n\n        print()\n\n        # Find matching RELA record in .rela.dyn in ESXi ELF files\n        # This is temporary code until proper ELF parsing written\n        if sharedobj:\n            print('Modifying RELA records from: ' + hex(smc_old_memptr) + ' to ' + hex(smc_new_memptr))\n            patchelf(f, smc_old_memptr, smc_new_memptr)\n\n        # Tidy up\n        f.flush()\n        f.close()\n\n\ndef patchvmkctl(name):\n    # Patch file\n    print('smcPresent Patching: ' + name)\n    f = open(name, 'r+b')\n\n    # Read file into string variable\n    vmkctl = f.read()\n    applesmc = vmkctl.find(b'applesmc')\n    f.seek(applesmc)\n    f.write(b'vmkernel')\n\n    # Tidy up\n    f.flush()\n    f.close()\n    print('smcPresent Patched: ' + name)\n\n\ndef main():\n\n    # Stop the hostd service\n    subprocess.call('/etc/init.d/hostd stop', shell=True)\n\n    # Current folder\n    currdir = os.getcwd()\n\n    # Source files\n    srcvmx = '/bin/vmx'\n    srclib32 = '/lib/libvmkctl.so'\n    srclib64 = '/lib64/libvmkctl.so'\n\n    # Destination files currently tmp but may use scratch\n    basefolder = '/tmp/'\n    destfolder = joinpath(basefolder, 'unlocker')\n    destvmx = joinpath(destfolder, 'bin/vmx')\n    destlib32 = joinpath(destfolder, 'lib/libvmkctl.so')\n    destlib64 = joinpath(destfolder, 'lib64/libvmkctl.so')\n\n    # Remove files & folder if they exist\n    if os.path.isdir(destfolder):\n        shutil.rmtree(destfolder, True)\n\n    # Create the base folder\n    os.makedirs(destfolder)\n    os.chdir(destfolder)\n\n    # Patch the vmx executable\n    os.makedirs(joinpath(destfolder, 'bin'))\n    shutil.copy2(srcvmx, destvmx)\n    patchsmc(destvmx, True)\n\n    # Patch 32-bit libvmkctl to return Apple SMC present\n    os.makedirs(joinpath(destfolder, 'lib'))\n    if os.path.isfile(srclib32):\n        shutil.copy2(srclib32, destlib32)\n        patchvmkctl(destlib32)\n\n    # Patch 64-bit libvmkctl to return Apple SMC present\n    if os.path.isfile(srclib64):\n        os.makedirs(joinpath(destfolder, 'lib64'))\n        shutil.copy2(srclib64, destlib64)\n        patchvmkctl(destlib64)\n\n    # Build the gzipped tar file custom.tgz\n    print('\\nCreating custom.tgz...')\n    subprocess.call('/bin/tar czvf custom.tgz bin lib lib64', shell=True)\n\n    # Build the vmtar file custom.vmtar\n    print('\\nCreating custom.vmtar...')\n    subprocess.call('/bin/vmtar -v -c custom.tgz -o custom.vmtar', shell=True)\n\n    # Build the gzipped vmtar file custom.vgz\n    print('\\nCreating custom.vgz...')\n    subprocess.call('/bin/gzip < custom.vmtar > custom.vgz', shell=True)\n    subprocess.call('/bin/vmtar -v -t < custom.vgz', shell=True)\n\n    # Load the tardisk\n    subprocess.call('vmkramdisk custom.vgz', shell=True)\n\n    # Return to script folder\n    os.chdir(currdir)\n\n    # Start the hostd service\n    subprocess.call('/etc/init.d/hostd start', shell=True)\n\n\nif __name__ == '__main__':\n    # Check boot options specified\n    bootoptions = subprocess.check_output(['/bin/bootOption', '-a'])\n    if bootoptions.find(b'nounlocker') == -1:\n        # Run unlocker code\n        main()\n    else:\n        # Exit if \"nounlocker\" boot option specified\n        print('Unlocker disabled by boot option')\n"
  },
  {
    "path": "readme.md",
    "content": "# macOS Unlocker V3.0.2 for VMware ESXi\n\n\n## 1. Introduction\n\n\nUnlocker 3 for ESXi is designed for VMware ESXi 6.5, 6.7 and 7.0\n\nThe patch code carries out the following modifications dependent on the product\nbeing patched:\n\n* Fix vmware-vmx to allow macOS to boot\n* Fix libvmkctl to allow vSphere to control the guest\n\nThe code is written in Python as it makes the Unlocker easier to run and\nmaintain on ESXi.\n\n>\n> *IMPORTANT:*                                                                                                                           |\n>                                                                           \n> Always uninstall the previous version of the Unlocker before using a new  \n> version. Failure to do this could render VMware unusable.                 \n\n## 2. Installation\n\nCopy the latest release file to the ESXi host datastore using scp or some other\ndata transfer system. If you want to use the source version (i.e. from GIT) see\n\"5. Building\" fist.\n\nDecompress the file from the ESXi console or via SSH:\n\n    tar xzvf esxi-unlocker-xxx.tgz\n\n(xxx - will be the version number, for example, 3.0.0)\n\nRun the command from the terminal:\n\n    ./esxi-install.sh\n\nFinally reboot the server.\n\n## 3. Uninstallation\n\nOpen the ESXi console or login via SSH and change to the folder where the files were extracted.\n\nRun the command from the terminal:\n\n    ./esxi-uninstall.sh\n\nFinally reboot the server.\n\n## 4. Notes\n\nA. There is a command added called esxi-smctest.sh which can show if the patch is successful. It must be run from a\nterminal or SSH session. The output should be:\n\n/bin/vmx\nsmcPresent = true\ncustom.vgz     false   32486592 B\n\nNote: The uncompressed size reported for custom.vgz will vary depending on the ESXi version.\n\nB. The unlocker can be temporarily disabled during boot by editing the boot options and adding \"nounlocker\".\n\n## 5. Building\n\nIf you want to use a version which is not available as a distribution (e.g. the code from \"master\" branch)\nyou need to first build the package.  You can build locally on a Mac or via a github workflow.\n\n### Local Build\n\nCheckout the repository:\n\n    git clone https://github.com/shanyungyang/esxi-unlocker.git\n\n(if you don't have git installed you can download ZIP archive from GitHub instead)\n\nEnter the directory and build:\n    \n    cd esxi-unlocker\n    ./esxi-build.py\n\nIf everything went correctly the ouput should be:\n\n    ESXi-Build for macOS\n\n    Timestamping files...\n\n    Creating unlocker.tgz...\n    etc/\n    etc/rc.local.d/\n    etc/rc.local.d/unlocker.py\n\n    Creating esxi-unlocker-301.tgz...\n    unlocker.tgz\n    esxi-install.sh\n    esxi-uninstall.sh\n    esxi-smctest.sh\n    readme.txt\n\nThe package you need to copy in the example above is esxi-unlocker-301.tgz (NOT unlocker.tgz!).\n\n###  Github Build\n\n#### Triggered\n\nIf you add a tag to any commit in the semver format `*.*.*` a triggered build will run and create a release.\n\n#### Manual\n\nFork the repository, click on actions, select manual CI and then run.  \n\nThis will build a new draft release for you which you can upload to your esxi using curl or wget.\n\nIf you add a tag in the format `*.*.*` a triggered build will run without needing \n\n## 6. Thanks\n\nThanks to Zenith432 for originally building the C++ unlocker and Mac Son of Knife\n(MSoK) for all the testing and support.\n\nThanks also to Sam B for finding the solution for ESXi 6 and helping me with\ndebugging expertise. Sam also wrote the code for patching ESXi ELF files and\nmodified the unlocker code to run on Python 3 in the ESXi 6.5 environment.\n\n# History\n\n26/09/18 3.0.0 - First release\n\n01/05/20 3.0.1 - Fix for ESXi 7.0\n\n10/18/20 3.0.2 - Fix for ESXi 7.0 U1 (7.0.1)\n\n10/29/20 3.0.3 - Release process automated\n\n(c) 2011-2018 Dave Parsons\n"
  }
]