Files
universal-shell-ext/build.py
T
Simone 6ac95e43cb chore: initial commit (extracted from Launchers monorepo)
Plugin: UniversalShellExt v1.0.0
Architectures: x64
License: MIT
2026-04-29 14:06:20 +02:00

272 lines
9.3 KiB
Python

#!/usr/bin/env python3
"""
Build script per UniversalShellExt64.dll
Compila, copia, firma e registra la shell extension generica.
Richiede Visual Studio 2022 e Windows SDK.
"""
import subprocess
import shutil
import sys
import os
from pathlib import Path
import winreg
# =============================================================================
# Configurazione
# =============================================================================
# Percorsi
SCRIPT_DIR = Path(__file__).parent.resolve()
SOURCE_FILE = SCRIPT_DIR / "UniversalShellExt.cpp"
DEF_FILE = SCRIPT_DIR / "UniversalShellExt.def"
RC_FILE = SCRIPT_DIR / "UniversalShellExt.rc"
RES_FILE = SCRIPT_DIR / "UniversalShellExt.res"
OUTPUT_DIR = SCRIPT_DIR / "bin" / "x64"
OUTPUT_DLL = OUTPUT_DIR / "UniversalShellExt64.dll"
# Destinazione finale (cartella plugins)
DEST_DLL = SCRIPT_DIR.parent.parent / "plugins" / "UniversalShellExt64.dll"
# Visual Studio
VS_PATH = Path("C:/Program Files/Microsoft Visual Studio/2022/Community")
VCVARS_BAT = VS_PATH / "VC/Auxiliary/Build/vcvars64.bat"
# Signtool
SIGNTOOL = Path("C:/Program Files (x86)/Windows Kits/10/bin/10.0.22621.0/x64/signtool.exe")
CERT_THUMBPRINT = "DFF03ABA0E0156D8E7823A893F9588530A364410"
TIMESTAMP_URL = "http://timestamp.digicert.com"
# Librerie
LIBS = [
"ole32.lib",
"uuid.lib",
"shell32.lib",
"user32.lib",
"gdi32.lib",
"advapi32.lib",
"shlwapi.lib"
]
CLSID_STRING = "{5D849F86-F2A9-41FA-8A24-E01CD6D6696C}"
def run_command(cmd, shell=False, check=True):
print(f" > {cmd if isinstance(cmd, str) else ' '.join(cmd)}")
result = subprocess.run(cmd, shell=shell, capture_output=True, text=True)
if result.stdout: print(result.stdout)
if result.stderr: print(result.stderr)
if check and result.returncode != 0:
print(f"ERRORE: Comando fallito con codice {result.returncode}")
sys.exit(1)
return result
def check_prerequisites():
print("\n=== Verifica prerequisiti ===")
errors = []
if not SOURCE_FILE.exists(): errors.append(f"Manca sorgente: {SOURCE_FILE}")
if not DEF_FILE.exists(): errors.append(f"Manca DEF: {DEF_FILE}")
if not VCVARS_BAT.exists(): errors.append(f"Manca VS: {VCVARS_BAT}")
if not SIGNTOOL.exists(): errors.append(f"Manca Signtool: {SIGNTOOL}")
if errors:
for e in errors: print(f" ERRORE: {e}")
sys.exit(1)
print(" Prerequisiti OK")
def update_version():
print("\n=== Incremento Versione ===")
import re
if not RC_FILE.exists():
print(" ERRORE: .rc non trovato!")
return
content = RC_FILE.read_text(encoding="utf-8")
# Cerca VER_FILEVERSION 1,0,0,X
# Regex per catturare i primi 3 numeri e l'ultimo
pattern = r'(#define VER_FILEVERSION\s+\d+,\d+,\d+,)(\d+)'
match = re.search(pattern, content)
if match:
prefix = match.group(1)
build_num = int(match.group(2))
new_build_num = build_num + 1
# Sostituisci VER_FILEVERSION
new_content = re.sub(pattern, f'{prefix}{new_build_num}', content)
# Aggiorna anche VER_FILEVERSION_STR "1.0.0.X\0"
str_pattern = r'(#define VER_FILEVERSION_STR\s+"[^"]*?)(\d+)(\\0")'
# Nota: questa regex assume che la stringa finisca col numero da incrementare.
# Se รจ "1.0.0.0\0", cattura "1.0.0." come gruppo 1, "0" come 2.
# Miglior approccio: ricostruire la stringa in base al nuovo numero
# Ma dobbiamo parsare i primi 3 numeri per essere sicuri
v_parts = prefix.strip().replace("#define VER_FILEVERSION", "").strip().split(',')
if len(v_parts) >= 3:
major, minor, patch = v_parts[0], v_parts[1], v_parts[2]
new_ver_str = f'{major}.{minor}.{patch}.{new_build_num}'
# Sostituisci la stringa intera con gestione corretta di virgolette e backslash
# La regex deve catturare tutto fino ai doppi apici finali
new_content = re.sub(r'(#define VER_FILEVERSION_STR\s+)"(.*?)"', f'#define VER_FILEVERSION_STR "{new_ver_str}\\\\0"', new_content)
# Aggiorna anche VER_PRODUCTVERSION (spesso uguale)
new_content = re.sub(r'(#define VER_PRODUCTVERSION\s+).*', f'#define VER_PRODUCTVERSION {major},{minor},{patch},{new_build_num}', new_content)
# Aggiorna VER_PRODUCTVERSION_STR (spesso solo 3 cifre, ma qui mettiamo 4 per coerenza o lasciamo stare?)
# Nel file attuale รจ "1.0.0\0". Se vogliamo incrementare anche quello:
# new_content = re.sub(r'#define VER_PRODUCTVERSION_STR\s+".*?"', f'#define VER_PRODUCTVERSION_STR "{major}.{minor}.{patch}\\0"', new_content)
RC_FILE.write_text(new_content, encoding="utf-8")
print(f" Versione aggiornata a: {new_ver_str}")
else:
print(" Non riesco a parsare la versione corrente.")
else:
print(" Pattern versione non trovato.")
def clean_build():
print("\n=== Clean build ===")
to_remove = [
SCRIPT_DIR / "bin",
SCRIPT_DIR / "Release",
SCRIPT_DIR / "Debug",
SCRIPT_DIR / "x64",
SCRIPT_DIR / "UniversalShellExt64.dll",
SCRIPT_DIR / "UniversalShellExt64.exp",
SCRIPT_DIR / "UniversalShellExt64.lib",
SCRIPT_DIR / "UniversalShellExt64.pdb",
SCRIPT_DIR / "UniversalShellExt.obj",
RES_FILE,
]
for path in to_remove:
if path.exists():
if path.is_dir():
try: shutil.rmtree(path)
except: pass
else:
try: path.unlink()
except: pass
print(" Clean completato")
def kill_explorer():
print("\n=== Termina Explorer ===")
subprocess.run(["taskkill", "/F", "/IM", "explorer.exe"], capture_output=True, check=False)
import time
time.sleep(2)
def start_explorer():
print("\n=== Riavvia Explorer ===")
subprocess.Popen(["explorer.exe"])
import time
time.sleep(2)
def unregister_dll():
print("\n=== Deregistra DLL ===")
if DEST_DLL.exists():
run_command(["regsvr32", "/u", "/s", str(DEST_DLL)], check=False)
print(" DLL deregistrata")
else:
print(" DLL non trovata, skip")
def compile_dll():
print("\n=== Compilazione DLL ===")
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
# Compile Resource
rc_cmd = f'''call "{VCVARS_BAT}" && cd /D "{SCRIPT_DIR}" && rc.exe /fo"{RES_FILE.name}" "{RC_FILE.name}"'''
subprocess.run(f'cmd /c "{rc_cmd}"', shell=True, check=True)
# Compile DLL
libs_str = " ".join(LIBS)
compile_cmd = f'''call "{VCVARS_BAT}" && cd /D "{SCRIPT_DIR}" && cl.exe /LD /EHsc /O2 /DUNICODE /D_UNICODE "{SOURCE_FILE.name}" "{RES_FILE.name}" /Fe:"{OUTPUT_DLL}" /link /DEF:"{DEF_FILE.name}" {libs_str}'''
result = subprocess.run(f'cmd /c "{compile_cmd}"', shell=True, capture_output=True, text=True)
if result.stdout:
for line in result.stdout.split('\n'):
if line.strip() and not line.startswith('**') and not line.startswith('['):
print(f" {line}")
if result.returncode != 0 or not OUTPUT_DLL.exists():
print("ERRORE: Compilazione fallita")
sys.exit(1)
print(f" Compilata: {OUTPUT_DLL}")
def copy_dll():
print("\n=== Copia DLL ===")
shutil.copy2(OUTPUT_DLL, DEST_DLL)
print(f" Copiata in: {DEST_DLL}")
def sign_dll():
print("\n=== Firma DLL ===")
cmd = [str(SIGNTOOL), "sign", "/sha1", CERT_THUMBPRINT, "/fd", "SHA256", "/t", TIMESTAMP_URL, str(DEST_DLL)]
run_command(cmd)
print(" Firma OK")
def register_dll():
print("\n=== Registra DLL ===")
result = run_command(["regsvr32", "/s", str(DEST_DLL)], check=False)
if result.returncode == 0:
print(" Registrazione OK")
else:
print(f" FALLITA: {result.returncode} (Serve Admin?)")
def verify_registration():
print("\n=== Verifica Registrazione ===")
try:
key_path = fr"SOFTWARE\Classes\CLSID\{CLSID_STRING}\InprocServer32"
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, key_path) as key:
val, _ = winreg.QueryValueEx(key, "")
print(f" CLSID registrato: {val}")
except FileNotFoundError:
print(" ERRORE: CLSID non trovato")
return False
try:
key_path = r"SOFTWARE\Classes\Directory\shellex\ContextMenuHandlers\UniversalShellExt"
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, key_path) as key:
val, _ = winreg.QueryValueEx(key, "")
print(f" Handler directory registrato: {val}")
except FileNotFoundError:
print(" ERRORE: Handler non trovato")
return False
return True
def main():
print("UniversalShellExt Builder")
args = sys.argv[1:]
if "--clean" in args:
clean_build()
return
if "--unregister" in args:
unregister_dll()
return
# STEP 0: Clean pre-build
clean_build()
update_version()
check_prerequisites()
unregister_dll()
kill_explorer()
compile_dll()
copy_dll()
if "--no-sign" not in args:
sign_dll()
start_explorer()
register_dll()
verify_registration()
clean_build()
print("\nDONE!")
print(f"DLL: {DEST_DLL}")
print("Ricorda di configurare HKLM\\Software\\UniversalShellExt")
if __name__ == "__main__":
main()