├── README.md ├── secure_emu ├── main.py ├── py.typed ├── symbol_client.py ├── machine_state.py ├── image_loader.py └── __init__.py ├── tests └── __init__.py ├── .idea ├── sonarlint │ └── issuestore │ │ └── index.pb ├── vcs.xml ├── misc.xml ├── inspectionProfiles │ └── profiles_settings.xml ├── discord.xml ├── .gitignore ├── modules.xml ├── secure_emu.iml └── aws.xml ├── .gitmodules ├── pyproject.toml └── poetry.lock /README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /secure_emu/main.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /secure_emu/py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/index.pb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /secure_emu/symbol_client.py: -------------------------------------------------------------------------------- 1 | class SymbolClient: 2 | pass 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ext/roms"] 2 | path = ext/roms 3 | url = https://github.com/hekapooios/hekapooios.github.io.git 4 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/discord.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | # Zeppelin ignored files 10 | /ZeppelinRemoteNotebooks/ 11 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/secure_emu.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/aws.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "secure-emu" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Rick Mark "] 6 | readme = "README.md" 7 | packages = [{include = "secure_emu"}] 8 | 9 | [tool.poetry.dependencies] 10 | python = "^3.10" 11 | apple-data = "^1.0.522" 12 | unicorn = "^2.0.0" 13 | capstone = "^4.0.2" 14 | keystone-engine = "^0.9.2" 15 | 16 | 17 | [tool.poetry.group.dev.dependencies] 18 | pytest = "^7.1.3" 19 | mypy = "^0.971" 20 | 21 | [build-system] 22 | requires = ["poetry-core"] 23 | build-backend = "poetry.core.masonry.api" 24 | -------------------------------------------------------------------------------- /secure_emu/machine_state.py: -------------------------------------------------------------------------------- 1 | import apple_data 2 | import unicorn 3 | 4 | 5 | class MachineState: 6 | def __init__(self): 7 | self.msr_data = apple_data.load_file("registers") 8 | 9 | def friendly_name(self, cp_reg: unicorn.unicorn.uc_arm64_cp_reg) -> str: 10 | msr_map = self.msr_data["aarch64"]["msr"] 11 | apple_map = self.msr_data["aarch64"]["apple_system_registers"] 12 | reg_descriptor = ( 13 | f"S{cp_reg.op0}_{cp_reg.op1}_c{cp_reg.crn}_c{cp_reg.crm}_{cp_reg.op2}" 14 | ) 15 | if reg_descriptor in msr_map: 16 | return msr_map[reg_descriptor] 17 | if reg_descriptor in apple_map: 18 | return apple_map[reg_descriptor] 19 | 20 | return "Unknown" 21 | -------------------------------------------------------------------------------- /secure_emu/image_loader.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | import os 3 | import typing 4 | import apple_data 5 | import hashlib 6 | 7 | 8 | class Core: 9 | pass 10 | 11 | 12 | class ImageLoader: 13 | ROMS: dict[str, pathlib.Path] = {} 14 | _cores: dict[str, typing.Any] = {} 15 | _loaded = False 16 | 17 | @staticmethod 18 | def get_securerom(path: str) -> bytes: 19 | rom_path = pathlib.Path(os.path.dirname(__file__)).joinpath(path) 20 | with open(rom_path, mode="rb") as file: 21 | return file.read() 22 | 23 | @staticmethod 24 | def get_core(core: int) -> Core: 25 | pass 26 | 27 | @staticmethod 28 | def _prepare(): 29 | if ImageLoader._loaded: 30 | return 31 | 32 | for image_path in pathlib.Path(os.path.join(os.path.dirname(__file__), '../ext/roms/resources/APROM')).glob( 33 | "*"): 34 | with open(image_path, 'rb') as f: 35 | data = f.read() 36 | ImageLoader.ROMS[hashlib.sha256(data).hexdigest()] = image_path 37 | 38 | ImageLoader._cores = apple_data.load_file('cores') 39 | 40 | ImageLoader._loaded = True 41 | 42 | -------------------------------------------------------------------------------- /secure_emu/__init__.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | import unicorn 4 | import machine_state 5 | import image_loader 6 | 7 | RegisterHook = typing.Callable[[int], int] 8 | 9 | 10 | class SecureEMU: 11 | _core: image_loader.Core 12 | register_bank = {} 13 | register_hooks: dict[str, RegisterHook] 14 | 15 | def __init__(self, core: int): 16 | self._core = image_loader.ImageLoader.get_core(core) 17 | self._machine_state = machine_state.MachineState() 18 | 19 | @staticmethod 20 | def cp_reg_to_id(cp_reg: unicorn.unicorn.uc_arm64_cp_reg) -> str: 21 | return f"c{cp_reg.crn}_c{cp_reg.crm}_{cp_reg.op1}_{cp_reg.op2}" 22 | 23 | @staticmethod 24 | def _hook_block(uc, address, size, user_data): 25 | print(">>> Tracing basic block at 0x%x, block size = 0x%x" % (address, size)) 26 | 27 | @staticmethod 28 | def _hook_code(uc, address, size, user_data): 29 | print( 30 | ">>> Tracing instruction at 0x%x, instruction size = 0x%x" % (address, size) 31 | ) 32 | 33 | @staticmethod 34 | def _hook_mrs(uc: unicorn.Uc, reg, cp_reg, reg_file) -> bool: 35 | pc = uc.reg_read(unicorn.arm64_const.UC_ARM64_REG_PC) 36 | reg_friendly = msr_util.friendly_name(cp_reg) 37 | print( 38 | f">>> Hook MRS read instruction ({pc:x}): reg = 0x{reg:x}(UC_ARM64_REG_X2) cp_reg = {cp_reg}\n>>>\t{reg_friendly}" 39 | ) 40 | reg_id = _cp_reg_to_id(cp_reg) 41 | if reg_id not in reg_file: 42 | reg_file[reg_id] = 0 43 | 44 | uc.reg_write(reg, reg_file[reg_id]) 45 | uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_PC, pc + 4) 46 | # Skip MRS instruction 47 | 48 | return True 49 | 50 | def _hook_msr(self, uc: unicorn.Uc, reg, cp_reg, reg_file) -> bool: 51 | pc = uc.reg_read(unicorn.arm64_const.UC_ARM64_REG_PC) 52 | reg_friendly = msr_util.friendly_name(cp_reg) 53 | print( 54 | f">>> Hook MSR store instruction ({pc:x}): reg = 0x{reg:x}(UC_ARM64_REG_X2) cp_reg = {cp_reg}\n>>>\t{reg_friendly}" 55 | ) 56 | reg_id = SecureEMU._cp_reg_to_id(cp_reg) 57 | reg_value = uc.reg_read(reg) 58 | if reg_id in self.register_hooks: 59 | value_to_store = self.register_hooks[reg_id](reg_value) 60 | reg_file[reg_id] = value_to_store 61 | else: 62 | reg_file[reg_id] = reg_value 63 | uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_PC, pc + 4) 64 | # Skip MRS instruction 65 | 66 | return True 67 | 68 | @staticmethod 69 | def _hook_mem_invalid(mu: unicorn.Uc, access, address, size, value, user_data): 70 | ip = mu.reg_read(unicorn.arm64_const.UC_ARM64_REG_PC) 71 | match access: 72 | case unicorn.UC_MEM_FETCH: 73 | access_type = "FETCH" 74 | case unicorn.UC_MEM_READ: 75 | access_type = "READ" 76 | case unicorn.UC_MEM_WRITE: 77 | access_type = "WRITE" 78 | case other: 79 | access_type = f"UNKNOWN <{access:x}>" 80 | 81 | error = f">>> {access_type} ACCESS at 0x{address:016x} from IP = 0x{ip:016x}, data size = {size}, data value = 0x{value:x}" 82 | print(error) 83 | 84 | def run(self): 85 | mu = unicorn.Uc(unicorn.UC_ARCH_ARM64, unicorn.UC_MODE_ARM) 86 | mu.mem_map(self._core.image_base, self._core.image_size, unicorn.UC_PROT_EXEC | unicorn.UC_PROT_READ) 87 | 88 | mu.mem_write(self._core.image_base, self._core.secure_rom) 89 | 90 | mu.mem_map(self._core.sram_base, self._core.sram_size, unicorn.UC_PROT_ALL) 91 | 92 | def cache_as_ram_helper(value) -> int: 93 | if value & 0x01: 94 | return 0x8000000000000000 | value 95 | else: 96 | return value 97 | 98 | self.register_hooks["c15_c7_3_0"] = cache_as_ram_helper 99 | 100 | 101 | 102 | 103 | 104 | 105 | # mu.hook_add(unicorn.UC_HOOK_BLOCK, hook_block) 106 | mu.hook_add(unicorn.UC_HOOK_MEM_FETCH_UNMAPPED, hook_mem_invalid) 107 | mu.hook_add(unicorn.UC_HOOK_MEM_READ_UNMAPPED, hook_mem_invalid) 108 | mu.hook_add(unicorn.UC_HOOK_MEM_WRITE_UNMAPPED, hook_mem_invalid) 109 | 110 | mu.hook_add( 111 | unicorn.UC_HOOK_INSN, 112 | hook_mrs, 113 | self.register_bank, 114 | 1, 115 | 0, 116 | unicorn.arm64_const.UC_ARM64_INS_MRS, 117 | ) 118 | mu.hook_add( 119 | unicorn.UC_HOOK_INSN, 120 | hook_msr, 121 | self.register_bank, 122 | 1, 123 | 0, 124 | unicorn.arm64_const.UC_ARM64_INS_MSR, 125 | ) 126 | 127 | try: 128 | mu.emu_start(0x100000000, 0x100000000 + len(rom), 0, 100000) 129 | except unicorn.UcError as e: 130 | print(e) 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "apple-data" 3 | version = "1.0.522" 4 | description = "Static data from https://docs.hackdiffe.rent" 5 | category = "main" 6 | optional = false 7 | python-versions = ">=3.8,<4.0" 8 | 9 | [[package]] 10 | name = "attrs" 11 | version = "22.1.0" 12 | description = "Classes Without Boilerplate" 13 | category = "dev" 14 | optional = false 15 | python-versions = ">=3.5" 16 | 17 | [package.extras] 18 | dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] 19 | docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] 20 | tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] 21 | tests_no_zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] 22 | 23 | [[package]] 24 | name = "capstone" 25 | version = "4.0.2" 26 | description = "Capstone disassembly engine" 27 | category = "main" 28 | optional = false 29 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 30 | 31 | [[package]] 32 | name = "colorama" 33 | version = "0.4.5" 34 | description = "Cross-platform colored terminal text." 35 | category = "dev" 36 | optional = false 37 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 38 | 39 | [[package]] 40 | name = "iniconfig" 41 | version = "1.1.1" 42 | description = "iniconfig: brain-dead simple config-ini parsing" 43 | category = "dev" 44 | optional = false 45 | python-versions = "*" 46 | 47 | [[package]] 48 | name = "keystone-engine" 49 | version = "0.9.2" 50 | description = "Keystone assembler engine" 51 | category = "main" 52 | optional = false 53 | python-versions = "*" 54 | 55 | [[package]] 56 | name = "mypy" 57 | version = "0.971" 58 | description = "Optional static typing for Python" 59 | category = "dev" 60 | optional = false 61 | python-versions = ">=3.6" 62 | 63 | [package.dependencies] 64 | mypy-extensions = ">=0.4.3" 65 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 66 | typing-extensions = ">=3.10" 67 | 68 | [package.extras] 69 | dmypy = ["psutil (>=4.0)"] 70 | python2 = ["typed-ast (>=1.4.0,<2)"] 71 | reports = ["lxml"] 72 | 73 | [[package]] 74 | name = "mypy-extensions" 75 | version = "0.4.3" 76 | description = "Experimental type system extensions for programs checked with the mypy typechecker." 77 | category = "dev" 78 | optional = false 79 | python-versions = "*" 80 | 81 | [[package]] 82 | name = "packaging" 83 | version = "21.3" 84 | description = "Core utilities for Python packages" 85 | category = "dev" 86 | optional = false 87 | python-versions = ">=3.6" 88 | 89 | [package.dependencies] 90 | pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" 91 | 92 | [[package]] 93 | name = "pluggy" 94 | version = "1.0.0" 95 | description = "plugin and hook calling mechanisms for python" 96 | category = "dev" 97 | optional = false 98 | python-versions = ">=3.6" 99 | 100 | [package.extras] 101 | dev = ["pre-commit", "tox"] 102 | testing = ["pytest", "pytest-benchmark"] 103 | 104 | [[package]] 105 | name = "py" 106 | version = "1.11.0" 107 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 108 | category = "dev" 109 | optional = false 110 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 111 | 112 | [[package]] 113 | name = "pyparsing" 114 | version = "3.0.9" 115 | description = "pyparsing module - Classes and methods to define and execute parsing grammars" 116 | category = "dev" 117 | optional = false 118 | python-versions = ">=3.6.8" 119 | 120 | [package.extras] 121 | diagrams = ["jinja2", "railroad-diagrams"] 122 | 123 | [[package]] 124 | name = "pytest" 125 | version = "7.1.3" 126 | description = "pytest: simple powerful testing with Python" 127 | category = "dev" 128 | optional = false 129 | python-versions = ">=3.7" 130 | 131 | [package.dependencies] 132 | attrs = ">=19.2.0" 133 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 134 | iniconfig = "*" 135 | packaging = "*" 136 | pluggy = ">=0.12,<2.0" 137 | py = ">=1.8.2" 138 | tomli = ">=1.0.0" 139 | 140 | [package.extras] 141 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] 142 | 143 | [[package]] 144 | name = "tomli" 145 | version = "2.0.1" 146 | description = "A lil' TOML parser" 147 | category = "dev" 148 | optional = false 149 | python-versions = ">=3.7" 150 | 151 | [[package]] 152 | name = "typing-extensions" 153 | version = "4.3.0" 154 | description = "Backported and Experimental Type Hints for Python 3.7+" 155 | category = "dev" 156 | optional = false 157 | python-versions = ">=3.7" 158 | 159 | [[package]] 160 | name = "unicorn" 161 | version = "2.0.0" 162 | description = "Unicorn CPU emulator engine" 163 | category = "main" 164 | optional = false 165 | python-versions = "*" 166 | 167 | [metadata] 168 | lock-version = "1.1" 169 | python-versions = "^3.10" 170 | content-hash = "13917463298515dcfcb76b2d1bf244d0aea172795bfcc7e379644c45c6eee62b" 171 | 172 | [metadata.files] 173 | apple-data = [ 174 | {file = "apple-data-1.0.522.tar.gz", hash = "sha256:bdb737414706a02158b66c648dc45adf396af0d357ecf89e41359d477eead397"}, 175 | {file = "apple_data-1.0.522-cp310-cp310-manylinux_2_31_x86_64.whl", hash = "sha256:e45337d06eb7757ce1ffda9ef99670c2438c33163ba4984d86903684270ec897"}, 176 | ] 177 | attrs = [ 178 | {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, 179 | {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, 180 | ] 181 | capstone = [ 182 | {file = "capstone-4.0.2-py2.py3-none-manylinux1_i686.whl", hash = "sha256:da442f979414cf27e4621e70e835880878c858ea438c4f0e957e132593579e37"}, 183 | {file = "capstone-4.0.2-py2.py3-none-manylinux1_x86_64.whl", hash = "sha256:9d1a9096c5f875b11290317722ed44bb6e7c52e50cc79d791f142bce968c49aa"}, 184 | {file = "capstone-4.0.2-py2.py3-none-win32.whl", hash = "sha256:c3d9b443d1adb40ee2d9a4e7341169b76476ddcf3a54c03793b16cdc7cd35c5a"}, 185 | {file = "capstone-4.0.2-py2.py3-none-win_amd64.whl", hash = "sha256:0d65ffe8620920976ceadedc769f22318f6f150a592368d8a735612367ac8a1a"}, 186 | {file = "capstone-4.0.2.tar.gz", hash = "sha256:2842913092c9b69fd903744bc1b87488e1451625460baac173056e1808ec1c66"}, 187 | ] 188 | colorama = [ 189 | {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, 190 | {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, 191 | ] 192 | iniconfig = [ 193 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, 194 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, 195 | ] 196 | keystone-engine = [ 197 | {file = "keystone-engine-0.9.2.tar.gz", hash = "sha256:2f7af62dab0ce6c2732dbb4f31cfa2184a8a149e280b96b92ebc0db84c6e50f5"}, 198 | {file = "keystone_engine-0.9.2-py2.py3-none-macosx_10_14_x86_64.whl", hash = "sha256:dafcc3d9450c239cbc54148855b79c4b387777099c6d054005c835768cf955f2"}, 199 | {file = "keystone_engine-0.9.2-py2.py3-none-manylinux1_i686.whl", hash = "sha256:9e04dea5a2b50509b7b707abdb395de42772c40faa36131ea94482fba8dd5d9f"}, 200 | {file = "keystone_engine-0.9.2-py2.py3-none-manylinux1_x86_64.whl", hash = "sha256:5a5316a34323620b1bba31dcfe9e4b4ca6f0c030e82fc7a151da7c8fbe81a379"}, 201 | {file = "keystone_engine-0.9.2-py2.py3-none-win32.whl", hash = "sha256:9f81e480904a405ef008f1d9f0e4a05e37d2bd83c5218a27136e1a294b02c1f6"}, 202 | {file = "keystone_engine-0.9.2-py2.py3-none-win_amd64.whl", hash = "sha256:c91db1ff16d9d094e00d1827107d1b4afd5e63ce19b491a0140e660635000e8b"}, 203 | ] 204 | mypy = [ 205 | {file = "mypy-0.971-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2899a3cbd394da157194f913a931edfd4be5f274a88041c9dc2d9cdcb1c315c"}, 206 | {file = "mypy-0.971-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:98e02d56ebe93981c41211c05adb630d1d26c14195d04d95e49cd97dbc046dc5"}, 207 | {file = "mypy-0.971-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:19830b7dba7d5356d3e26e2427a2ec91c994cd92d983142cbd025ebe81d69cf3"}, 208 | {file = "mypy-0.971-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:02ef476f6dcb86e6f502ae39a16b93285fef97e7f1ff22932b657d1ef1f28655"}, 209 | {file = "mypy-0.971-cp310-cp310-win_amd64.whl", hash = "sha256:25c5750ba5609a0c7550b73a33deb314ecfb559c350bb050b655505e8aed4103"}, 210 | {file = "mypy-0.971-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d3348e7eb2eea2472db611486846742d5d52d1290576de99d59edeb7cd4a42ca"}, 211 | {file = "mypy-0.971-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3fa7a477b9900be9b7dd4bab30a12759e5abe9586574ceb944bc29cddf8f0417"}, 212 | {file = "mypy-0.971-cp36-cp36m-win_amd64.whl", hash = "sha256:2ad53cf9c3adc43cf3bea0a7d01a2f2e86db9fe7596dfecb4496a5dda63cbb09"}, 213 | {file = "mypy-0.971-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:855048b6feb6dfe09d3353466004490b1872887150c5bb5caad7838b57328cc8"}, 214 | {file = "mypy-0.971-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:23488a14a83bca6e54402c2e6435467a4138785df93ec85aeff64c6170077fb0"}, 215 | {file = "mypy-0.971-cp37-cp37m-win_amd64.whl", hash = "sha256:4b21e5b1a70dfb972490035128f305c39bc4bc253f34e96a4adf9127cf943eb2"}, 216 | {file = "mypy-0.971-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9796a2ba7b4b538649caa5cecd398d873f4022ed2333ffde58eaf604c4d2cb27"}, 217 | {file = "mypy-0.971-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5a361d92635ad4ada1b1b2d3630fc2f53f2127d51cf2def9db83cba32e47c856"}, 218 | {file = "mypy-0.971-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b793b899f7cf563b1e7044a5c97361196b938e92f0a4343a5d27966a53d2ec71"}, 219 | {file = "mypy-0.971-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d1ea5d12c8e2d266b5fb8c7a5d2e9c0219fedfeb493b7ed60cd350322384ac27"}, 220 | {file = "mypy-0.971-cp38-cp38-win_amd64.whl", hash = "sha256:23c7ff43fff4b0df93a186581885c8512bc50fc4d4910e0f838e35d6bb6b5e58"}, 221 | {file = "mypy-0.971-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1f7656b69974a6933e987ee8ffb951d836272d6c0f81d727f1d0e2696074d9e6"}, 222 | {file = "mypy-0.971-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d2022bfadb7a5c2ef410d6a7c9763188afdb7f3533f22a0a32be10d571ee4bbe"}, 223 | {file = "mypy-0.971-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef943c72a786b0f8d90fd76e9b39ce81fb7171172daf84bf43eaf937e9f220a9"}, 224 | {file = "mypy-0.971-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d744f72eb39f69312bc6c2abf8ff6656973120e2eb3f3ec4f758ed47e414a4bf"}, 225 | {file = "mypy-0.971-cp39-cp39-win_amd64.whl", hash = "sha256:77a514ea15d3007d33a9e2157b0ba9c267496acf12a7f2b9b9f8446337aac5b0"}, 226 | {file = "mypy-0.971-py3-none-any.whl", hash = "sha256:0d054ef16b071149917085f51f89555a576e2618d5d9dd70bd6eea6410af3ac9"}, 227 | {file = "mypy-0.971.tar.gz", hash = "sha256:40b0f21484238269ae6a57200c807d80debc6459d444c0489a102d7c6a75fa56"}, 228 | ] 229 | mypy-extensions = [ 230 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, 231 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, 232 | ] 233 | packaging = [ 234 | {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, 235 | {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, 236 | ] 237 | pluggy = [ 238 | {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, 239 | {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, 240 | ] 241 | py = [ 242 | {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, 243 | {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, 244 | ] 245 | pyparsing = [ 246 | {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, 247 | {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, 248 | ] 249 | pytest = [ 250 | {file = "pytest-7.1.3-py3-none-any.whl", hash = "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7"}, 251 | {file = "pytest-7.1.3.tar.gz", hash = "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39"}, 252 | ] 253 | tomli = [ 254 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 255 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 256 | ] 257 | typing-extensions = [ 258 | {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, 259 | {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, 260 | ] 261 | unicorn = [ 262 | {file = "unicorn-2.0.0-py2.py3-none-macosx_10_15_x86_64.whl", hash = "sha256:6a28c53d5aa034db24ced440be1ff5d33061b96a7c9b14bed624b071e8925bc3"}, 263 | {file = "unicorn-2.0.0-py2.py3-none-manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1efd78f49c3d14fa83159d876fb2cafd58c1ba8d3f61dacbc033f8b77c121774"}, 264 | {file = "unicorn-2.0.0-py2.py3-none-manylinux1_i686.whl", hash = "sha256:4a4ed8c325d77ac051c00d7c6912fdee9a0d48daaecadd34107ec649f728d7a7"}, 265 | {file = "unicorn-2.0.0-py2.py3-none-manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6053ec25481dac82e5a92b0676885c9f526c41415bf55b652ee3919f02ad513e"}, 266 | {file = "unicorn-2.0.0-py2.py3-none-manylinux1_x86_64.whl", hash = "sha256:10bfc2e45a4bc31bc6cd4325977e18335733ec8fadd85cf35b056ee939f4f046"}, 267 | {file = "unicorn-2.0.0-py2.py3-none-win32.whl", hash = "sha256:6798197cc1204ded0cd6286cc207ed1d2c017aed868e9dee53c5808ae86e8ddb"}, 268 | {file = "unicorn-2.0.0-py2.py3-none-win_amd64.whl", hash = "sha256:98d4630a67b19e2840e2a6423b8537c5ead9123bb8db0e6ff92c38701b95d62c"}, 269 | {file = "unicorn-2.0.0.tar.gz", hash = "sha256:9394b83798080e25a3921e0a80196f902ddd1391f1bf4a6b1fb6b8ccb63b41e8"}, 270 | ] 271 | --------------------------------------------------------------------------------