cros_ec_python.devices.pawnio
1import struct 2import warnings 3import ctypes 4import os 5import sys 6from ctypes import wintypes 7from typing import Iterable 8 9# Workaround for pdoc failing on Linux 10if os.name == 'nt': 11 import winreg 12 13from ..baseclass import CrosEcClass 14from ..constants.COMMON import * 15from ..exceptions import ECError 16 17 18class CrosEcPawnIO(CrosEcClass): 19 """ 20 Class to interact with the EC using the Windows PawnIO driver. 21 """ 22 23 def __init__(self, dll: str | None = None, bin: str | None = None): 24 """ 25 Initialise the EC using the PawnIO LpcCrOSEC driver. 26 :param dll: Path to the DLL to use. If None, will use the default path. 27 :param bin: Path to the binary to load. If None, will use the default path. 28 """ 29 30 if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'): 31 self.bin = bin or os.path.join(sys._MEIPASS, "LpcCrOSEC.bin") 32 else: 33 self.bin = bin or "LpcCrOSEC.bin" 34 35 if not dll: 36 dll = self._get_location() 37 self.pawniolib = ctypes.OleDLL(dll) 38 39 self.pawniolib.pawnio_version.argtypes = [ctypes.POINTER(wintypes.ULONG)] 40 self.pawniolib.pawnio_open.argtypes = [ctypes.POINTER(wintypes.HANDLE)] 41 self.pawniolib.pawnio_load.argtypes = [ 42 wintypes.HANDLE, 43 ctypes.POINTER(ctypes.c_ubyte), 44 ctypes.c_size_t, 45 ] 46 self.pawniolib.pawnio_execute.argtypes = [ 47 wintypes.HANDLE, 48 ctypes.c_char_p, 49 ctypes.POINTER(ctypes.c_ulonglong), 50 ctypes.c_size_t, 51 ctypes.POINTER(ctypes.c_ulonglong), 52 ctypes.c_size_t, 53 ctypes.POINTER(ctypes.c_size_t), 54 ] 55 self.pawniolib.pawnio_close.argtypes = [wintypes.HANDLE] 56 57 self.ec_init() 58 59 def __del__(self): 60 self.ec_exit() 61 62 def _pawnio_version(self) -> str: 63 version = wintypes.ULONG() 64 self.pawniolib.pawnio_version(ctypes.byref(version)) 65 major, minor, patch = ( 66 version.value >> 16, 67 (version.value >> 8) & 0xFF, 68 version.value & 0xFF, 69 ) 70 return f"{major}.{minor}.{patch}" 71 72 def _pawnio_open(self) -> None: 73 self.handle = wintypes.HANDLE() 74 self.pawniolib.pawnio_open(ctypes.byref(self.handle)) 75 76 def _pawnio_load(self, filepath: str) -> None: 77 with open(filepath, "rb") as file: 78 blob = file.read() 79 size = len(blob) 80 blob_array = (ctypes.c_ubyte * size)(*blob) 81 self.pawniolib.pawnio_load(self.handle, blob_array, size) 82 83 def _pawnio_execute( 84 self, 85 function: str, 86 in_data: Iterable, 87 out_size: int, 88 in_size: int | None = None, 89 ) -> tuple[int | ctypes.Array]: 90 function_bytes = function.encode("utf-8") 91 in_size = in_size if in_size is not None else len(in_data) 92 in_array = (ctypes.c_uint64 * in_size)(*in_data) 93 out_array = (ctypes.c_uint64 * out_size)() 94 return_size = ctypes.c_size_t() 95 96 self.pawniolib.pawnio_execute( 97 self.handle, 98 function_bytes, 99 in_array, 100 in_size, 101 out_array, 102 out_size, 103 ctypes.byref(return_size), 104 ) 105 106 return (return_size.value, out_array) 107 108 def _pawnio_close(self): 109 self.pawniolib.pawnio_close(self.handle) 110 111 @staticmethod 112 def _get_location() -> str | None: 113 """ 114 Get the location of the PawnIO driver. 115 """ 116 try: 117 with winreg.OpenKey( 118 winreg.HKEY_LOCAL_MACHINE, 119 r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\PawnIO" 120 ) as key: 121 return winreg.QueryValueEx(key, "InstallLocation")[0] + r"\\PawnIOLib.dll" 122 except FileNotFoundError: 123 fallback = os.environ.get("ProgramFiles", r"C:\Program Files") + r"\PawnIO\PawnIOLib.dll" 124 if os.path.exists(fallback): 125 return fallback 126 return None 127 128 @staticmethod 129 def detect() -> bool: 130 """ 131 Detect if the PawnIO driver is installed. 132 """ 133 return CrosEcPawnIO._get_location() is not None 134 135 def ec_init(self) -> None: 136 self._pawnio_open() 137 self._pawnio_load(self.bin) 138 139 def ec_exit(self) -> None: 140 """ 141 Close the file on exit. 142 """ 143 if hasattr(self, "handle"): 144 self._pawnio_close() 145 146 def command( 147 self, 148 version: Int32, 149 command: Int32, 150 outsize: Int32, 151 insize: Int32, 152 data: bytes = None, 153 warn: bool = True, 154 ) -> bytes: 155 """ 156 Send a command to the EC and return the response. 157 :param version: Command version number (often 0). 158 :param command: Command to send (EC_CMD_...). 159 :param outsize: Outgoing length in bytes. 160 :param insize: Max number of bytes to accept from the EC. 161 :param data: Outgoing data to EC. 162 :param warn: Whether to warn if the response size is not as expected. Default is True. 163 :return: Incoming data from EC. 164 """ 165 166 pawn_in_data = [version, command, outsize, insize] 167 # Convert data to 64-bit cells 168 for i in range(0, len(data or ()), 8): 169 if i + 8 > len(data): 170 # If the data is not a multiple of 8, pad it with zeros 171 pawn_in_data.append(struct.unpack('<Q', data[i:i+8] + b'\x00' * (8 - len(data[i:i+8])))[0]) 172 else: 173 pawn_in_data.append(struct.unpack('<Q', data[i:i+8])[0]) 174 175 # Convert insize from bytes to 64bit cells, + 1 for the EC result 176 pawn_out_size = -(-insize//8) + 1 177 178 size, res = self._pawnio_execute( 179 "ioctl_ec_command", 180 pawn_in_data, 181 pawn_out_size 182 ) 183 184 if size != pawn_out_size and warn: 185 warnings.warn( 186 f"Expected {pawn_out_size} bytes, got {size} back from PawnIO", 187 RuntimeWarning, 188 ) 189 190 # If the first cell is negative, it has failed 191 # Also convert to signed 192 if res[0] & (1 << 63): 193 signed_res = res[0] - (1 << 64) 194 raise ECError(-signed_res) 195 196 # Otherwise it's the length 197 if res[0] != insize and warn: 198 warnings.warn( 199 f"Expected {pawn_out_size} bytes, got {res[0]} back from EC", 200 RuntimeWarning, 201 ) 202 return bytes(res)[8:insize+8] 203 204 def memmap(self, offset: Int32, num_bytes: Int32) -> bytes: 205 """ 206 Read memory from the EC. 207 :param offset: Offset to read from. 208 :param num_bytes: Number of bytes to read. 209 :return: Bytes read from the EC. 210 """ 211 size, res = self._pawnio_execute( 212 "ioctl_ec_readmem", (offset, num_bytes), -(-num_bytes//8) 213 ) 214 return bytes(res)[:num_bytes]
19class CrosEcPawnIO(CrosEcClass): 20 """ 21 Class to interact with the EC using the Windows PawnIO driver. 22 """ 23 24 def __init__(self, dll: str | None = None, bin: str | None = None): 25 """ 26 Initialise the EC using the PawnIO LpcCrOSEC driver. 27 :param dll: Path to the DLL to use. If None, will use the default path. 28 :param bin: Path to the binary to load. If None, will use the default path. 29 """ 30 31 if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'): 32 self.bin = bin or os.path.join(sys._MEIPASS, "LpcCrOSEC.bin") 33 else: 34 self.bin = bin or "LpcCrOSEC.bin" 35 36 if not dll: 37 dll = self._get_location() 38 self.pawniolib = ctypes.OleDLL(dll) 39 40 self.pawniolib.pawnio_version.argtypes = [ctypes.POINTER(wintypes.ULONG)] 41 self.pawniolib.pawnio_open.argtypes = [ctypes.POINTER(wintypes.HANDLE)] 42 self.pawniolib.pawnio_load.argtypes = [ 43 wintypes.HANDLE, 44 ctypes.POINTER(ctypes.c_ubyte), 45 ctypes.c_size_t, 46 ] 47 self.pawniolib.pawnio_execute.argtypes = [ 48 wintypes.HANDLE, 49 ctypes.c_char_p, 50 ctypes.POINTER(ctypes.c_ulonglong), 51 ctypes.c_size_t, 52 ctypes.POINTER(ctypes.c_ulonglong), 53 ctypes.c_size_t, 54 ctypes.POINTER(ctypes.c_size_t), 55 ] 56 self.pawniolib.pawnio_close.argtypes = [wintypes.HANDLE] 57 58 self.ec_init() 59 60 def __del__(self): 61 self.ec_exit() 62 63 def _pawnio_version(self) -> str: 64 version = wintypes.ULONG() 65 self.pawniolib.pawnio_version(ctypes.byref(version)) 66 major, minor, patch = ( 67 version.value >> 16, 68 (version.value >> 8) & 0xFF, 69 version.value & 0xFF, 70 ) 71 return f"{major}.{minor}.{patch}" 72 73 def _pawnio_open(self) -> None: 74 self.handle = wintypes.HANDLE() 75 self.pawniolib.pawnio_open(ctypes.byref(self.handle)) 76 77 def _pawnio_load(self, filepath: str) -> None: 78 with open(filepath, "rb") as file: 79 blob = file.read() 80 size = len(blob) 81 blob_array = (ctypes.c_ubyte * size)(*blob) 82 self.pawniolib.pawnio_load(self.handle, blob_array, size) 83 84 def _pawnio_execute( 85 self, 86 function: str, 87 in_data: Iterable, 88 out_size: int, 89 in_size: int | None = None, 90 ) -> tuple[int | ctypes.Array]: 91 function_bytes = function.encode("utf-8") 92 in_size = in_size if in_size is not None else len(in_data) 93 in_array = (ctypes.c_uint64 * in_size)(*in_data) 94 out_array = (ctypes.c_uint64 * out_size)() 95 return_size = ctypes.c_size_t() 96 97 self.pawniolib.pawnio_execute( 98 self.handle, 99 function_bytes, 100 in_array, 101 in_size, 102 out_array, 103 out_size, 104 ctypes.byref(return_size), 105 ) 106 107 return (return_size.value, out_array) 108 109 def _pawnio_close(self): 110 self.pawniolib.pawnio_close(self.handle) 111 112 @staticmethod 113 def _get_location() -> str | None: 114 """ 115 Get the location of the PawnIO driver. 116 """ 117 try: 118 with winreg.OpenKey( 119 winreg.HKEY_LOCAL_MACHINE, 120 r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\PawnIO" 121 ) as key: 122 return winreg.QueryValueEx(key, "InstallLocation")[0] + r"\\PawnIOLib.dll" 123 except FileNotFoundError: 124 fallback = os.environ.get("ProgramFiles", r"C:\Program Files") + r"\PawnIO\PawnIOLib.dll" 125 if os.path.exists(fallback): 126 return fallback 127 return None 128 129 @staticmethod 130 def detect() -> bool: 131 """ 132 Detect if the PawnIO driver is installed. 133 """ 134 return CrosEcPawnIO._get_location() is not None 135 136 def ec_init(self) -> None: 137 self._pawnio_open() 138 self._pawnio_load(self.bin) 139 140 def ec_exit(self) -> None: 141 """ 142 Close the file on exit. 143 """ 144 if hasattr(self, "handle"): 145 self._pawnio_close() 146 147 def command( 148 self, 149 version: Int32, 150 command: Int32, 151 outsize: Int32, 152 insize: Int32, 153 data: bytes = None, 154 warn: bool = True, 155 ) -> bytes: 156 """ 157 Send a command to the EC and return the response. 158 :param version: Command version number (often 0). 159 :param command: Command to send (EC_CMD_...). 160 :param outsize: Outgoing length in bytes. 161 :param insize: Max number of bytes to accept from the EC. 162 :param data: Outgoing data to EC. 163 :param warn: Whether to warn if the response size is not as expected. Default is True. 164 :return: Incoming data from EC. 165 """ 166 167 pawn_in_data = [version, command, outsize, insize] 168 # Convert data to 64-bit cells 169 for i in range(0, len(data or ()), 8): 170 if i + 8 > len(data): 171 # If the data is not a multiple of 8, pad it with zeros 172 pawn_in_data.append(struct.unpack('<Q', data[i:i+8] + b'\x00' * (8 - len(data[i:i+8])))[0]) 173 else: 174 pawn_in_data.append(struct.unpack('<Q', data[i:i+8])[0]) 175 176 # Convert insize from bytes to 64bit cells, + 1 for the EC result 177 pawn_out_size = -(-insize//8) + 1 178 179 size, res = self._pawnio_execute( 180 "ioctl_ec_command", 181 pawn_in_data, 182 pawn_out_size 183 ) 184 185 if size != pawn_out_size and warn: 186 warnings.warn( 187 f"Expected {pawn_out_size} bytes, got {size} back from PawnIO", 188 RuntimeWarning, 189 ) 190 191 # If the first cell is negative, it has failed 192 # Also convert to signed 193 if res[0] & (1 << 63): 194 signed_res = res[0] - (1 << 64) 195 raise ECError(-signed_res) 196 197 # Otherwise it's the length 198 if res[0] != insize and warn: 199 warnings.warn( 200 f"Expected {pawn_out_size} bytes, got {res[0]} back from EC", 201 RuntimeWarning, 202 ) 203 return bytes(res)[8:insize+8] 204 205 def memmap(self, offset: Int32, num_bytes: Int32) -> bytes: 206 """ 207 Read memory from the EC. 208 :param offset: Offset to read from. 209 :param num_bytes: Number of bytes to read. 210 :return: Bytes read from the EC. 211 """ 212 size, res = self._pawnio_execute( 213 "ioctl_ec_readmem", (offset, num_bytes), -(-num_bytes//8) 214 ) 215 return bytes(res)[:num_bytes]
Class to interact with the EC using the Windows PawnIO driver.
CrosEcPawnIO(dll: str | None = None, bin: str | None = None)
24 def __init__(self, dll: str | None = None, bin: str | None = None): 25 """ 26 Initialise the EC using the PawnIO LpcCrOSEC driver. 27 :param dll: Path to the DLL to use. If None, will use the default path. 28 :param bin: Path to the binary to load. If None, will use the default path. 29 """ 30 31 if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'): 32 self.bin = bin or os.path.join(sys._MEIPASS, "LpcCrOSEC.bin") 33 else: 34 self.bin = bin or "LpcCrOSEC.bin" 35 36 if not dll: 37 dll = self._get_location() 38 self.pawniolib = ctypes.OleDLL(dll) 39 40 self.pawniolib.pawnio_version.argtypes = [ctypes.POINTER(wintypes.ULONG)] 41 self.pawniolib.pawnio_open.argtypes = [ctypes.POINTER(wintypes.HANDLE)] 42 self.pawniolib.pawnio_load.argtypes = [ 43 wintypes.HANDLE, 44 ctypes.POINTER(ctypes.c_ubyte), 45 ctypes.c_size_t, 46 ] 47 self.pawniolib.pawnio_execute.argtypes = [ 48 wintypes.HANDLE, 49 ctypes.c_char_p, 50 ctypes.POINTER(ctypes.c_ulonglong), 51 ctypes.c_size_t, 52 ctypes.POINTER(ctypes.c_ulonglong), 53 ctypes.c_size_t, 54 ctypes.POINTER(ctypes.c_size_t), 55 ] 56 self.pawniolib.pawnio_close.argtypes = [wintypes.HANDLE] 57 58 self.ec_init()
Initialise the EC using the PawnIO LpcCrOSEC driver.
Parameters
- dll: Path to the DLL to use. If None, will use the default path.
- bin: Path to the binary to load. If None, will use the default path.
@staticmethod
def
detect() -> bool:
129 @staticmethod 130 def detect() -> bool: 131 """ 132 Detect if the PawnIO driver is installed. 133 """ 134 return CrosEcPawnIO._get_location() is not None
Detect if the PawnIO driver is installed.
def
ec_init(self) -> None:
Initialise the EC, this shouldn't need to be called unless the keyword arg init was set to false.
def
ec_exit(self) -> None:
140 def ec_exit(self) -> None: 141 """ 142 Close the file on exit. 143 """ 144 if hasattr(self, "handle"): 145 self._pawnio_close()
Close the file on exit.
def
command( self, version: int, command: int, outsize: int, insize: int, data: bytes = None, warn: bool = True) -> bytes:
147 def command( 148 self, 149 version: Int32, 150 command: Int32, 151 outsize: Int32, 152 insize: Int32, 153 data: bytes = None, 154 warn: bool = True, 155 ) -> bytes: 156 """ 157 Send a command to the EC and return the response. 158 :param version: Command version number (often 0). 159 :param command: Command to send (EC_CMD_...). 160 :param outsize: Outgoing length in bytes. 161 :param insize: Max number of bytes to accept from the EC. 162 :param data: Outgoing data to EC. 163 :param warn: Whether to warn if the response size is not as expected. Default is True. 164 :return: Incoming data from EC. 165 """ 166 167 pawn_in_data = [version, command, outsize, insize] 168 # Convert data to 64-bit cells 169 for i in range(0, len(data or ()), 8): 170 if i + 8 > len(data): 171 # If the data is not a multiple of 8, pad it with zeros 172 pawn_in_data.append(struct.unpack('<Q', data[i:i+8] + b'\x00' * (8 - len(data[i:i+8])))[0]) 173 else: 174 pawn_in_data.append(struct.unpack('<Q', data[i:i+8])[0]) 175 176 # Convert insize from bytes to 64bit cells, + 1 for the EC result 177 pawn_out_size = -(-insize//8) + 1 178 179 size, res = self._pawnio_execute( 180 "ioctl_ec_command", 181 pawn_in_data, 182 pawn_out_size 183 ) 184 185 if size != pawn_out_size and warn: 186 warnings.warn( 187 f"Expected {pawn_out_size} bytes, got {size} back from PawnIO", 188 RuntimeWarning, 189 ) 190 191 # If the first cell is negative, it has failed 192 # Also convert to signed 193 if res[0] & (1 << 63): 194 signed_res = res[0] - (1 << 64) 195 raise ECError(-signed_res) 196 197 # Otherwise it's the length 198 if res[0] != insize and warn: 199 warnings.warn( 200 f"Expected {pawn_out_size} bytes, got {res[0]} back from EC", 201 RuntimeWarning, 202 ) 203 return bytes(res)[8:insize+8]
Send a command to the EC and return the response.
Parameters
- version: Command version number (often 0).
- command: Command to send (EC_CMD_...).
- outsize: Outgoing length in bytes.
- insize: Max number of bytes to accept from the EC.
- data: Outgoing data to EC.
- warn: Whether to warn if the response size is not as expected. Default is True.
Returns
Incoming data from EC.
def
memmap(self, offset: int, num_bytes: int) -> bytes:
205 def memmap(self, offset: Int32, num_bytes: Int32) -> bytes: 206 """ 207 Read memory from the EC. 208 :param offset: Offset to read from. 209 :param num_bytes: Number of bytes to read. 210 :return: Bytes read from the EC. 211 """ 212 size, res = self._pawnio_execute( 213 "ioctl_ec_readmem", (offset, num_bytes), -(-num_bytes//8) 214 ) 215 return bytes(res)[:num_bytes]
Read memory from the EC.
Parameters
- offset: Offset to read from.
- num_bytes: Number of bytes to read.
Returns
Bytes read from the EC.