@@ -30,7 +30,9 @@ const F32_BYTES: usize = std::mem::size_of::<f32>();
3030const HEADER_BYTES_V1 : usize = 9 ;
3131const HEADER_BYTES_V2 : usize = 13 ;
3232const ONNX_RUNTIME_INSTALL_HINT : & str =
33- "ONNX Runtime not found. Install via: brew install onnxruntime (macOS) or apt install libonnxruntime (Linux)." ;
33+ "ONNX Runtime not found. Install via: brew install onnxruntime (macOS), \
34+ apt install libonnxruntime (Linux), or place onnxruntime.dll in your PATH (Windows). \
35+ AFT can auto-download ONNX Runtime — run `npx @cortexkit/aft doctor` to diagnose.";
3436
3537const SEMANTIC_INDEX_VERSION_V1 : u8 = 1 ;
3638const SEMANTIC_INDEX_VERSION_V2 : u8 = 2 ;
@@ -788,8 +790,136 @@ pub fn pre_validate_onnx_runtime() -> Result<(), String> {
788790
789791 #[ cfg( target_os = "windows" ) ]
790792 {
791- // On Windows, skip pre-validation — let ort handle LoadLibrary
792- let _ = dylib_path;
793+ // Validate ONNX Runtime availability on Windows by loading the DLL
794+ // via LoadLibraryExW before the ort crate attempts its own LoadLibrary.
795+ // This way we can produce a friendly error (with installation hints)
796+ // instead of a raw LoadLibrary failure from deep inside fastembed.
797+ let lib_name = dylib_path. as_deref ( ) . unwrap_or ( "onnxruntime.dll" ) ;
798+
799+ // Use kernel32 LoadLibraryExW for the validation — built-in, no
800+ // crate dependency required. GetModuleFileNameW resolves the loaded
801+ // DLL path for version probing via the version.dll API.
802+ #[ link( name = "kernel32" ) ]
803+ extern "system" {
804+ fn LoadLibraryExW (
805+ lpLibFileName : * const u16 ,
806+ hFile : * mut std:: ffi:: c_void ,
807+ dwFlags : u32 ,
808+ ) -> * mut std:: ffi:: c_void ;
809+ fn FreeLibrary ( hLibModule : * mut std:: ffi:: c_void ) -> i32 ;
810+ fn GetModuleFileNameW (
811+ hModule : * mut std:: ffi:: c_void ,
812+ lpFilename : * mut u16 ,
813+ nSize : u32 ,
814+ ) -> u32 ;
815+ }
816+
817+ #[ link( name = "version" ) ]
818+ extern "system" {
819+ fn GetFileVersionInfoSizeW (
820+ lptstrFilename : * const u16 ,
821+ lpdwHandle : * mut u32 ,
822+ ) -> u32 ;
823+ fn GetFileVersionInfoW (
824+ lptstrFilename : * const u16 ,
825+ dwHandle : u32 ,
826+ dwLen : u32 ,
827+ lpData : * mut std:: ffi:: c_void ,
828+ ) -> i32 ;
829+ fn VerQueryValueW (
830+ pBlock : * mut std:: ffi:: c_void ,
831+ lpSubBlock : * const u16 ,
832+ lplpBuffer : * mut * mut std:: ffi:: c_void ,
833+ puLen : * mut u32 ,
834+ ) -> i32 ;
835+ }
836+
837+ #[ repr( C ) ]
838+ struct VS_FIXEDFILEINFO {
839+ dw_signature : u32 ,
840+ dw_struc_version : u32 ,
841+ dw_file_version_ms : u32 , // HIWORD major, LOWORD minor
842+ dw_file_version_ls : u32 , // HIWORD build, LOWORD revision
843+ dw_product_version_ms : u32 ,
844+ dw_product_version_ls : u32 ,
845+ dw_file_flags_mask : u32 ,
846+ dw_file_flags : u32 ,
847+ dw_file_os : u32 ,
848+ dw_file_type : u32 ,
849+ dw_file_subtype : u32 ,
850+ dw_file_date_ms : u32 ,
851+ dw_file_date_ls : u32 ,
852+ }
853+
854+ unsafe {
855+ use std:: os:: windows:: ffi:: OsStrExt ;
856+ let wide: Vec < u16 > = std:: ffi:: OsStr :: new ( lib_name)
857+ . encode_wide ( )
858+ . chain ( std:: iter:: once ( 0 ) )
859+ . collect ( ) ;
860+
861+ let handle = LoadLibraryExW ( wide. as_ptr ( ) , std:: ptr:: null_mut ( ) , 0 ) ;
862+ if handle. is_null ( ) {
863+ let err = std:: io:: Error :: last_os_error ( ) ;
864+ return Err ( format ! (
865+ "ONNX Runtime not found. LoadLibraryExW('{}') failed: {}. \
866+ Run `npx @cortexkit/aft doctor` to diagnose.",
867+ lib_name, err
868+ ) ) ;
869+ }
870+
871+ // Probe the file version from PE resources so we can reject
872+ // outdated DLLs (e.g. v1.9.x) before the ort crate panics.
873+ let mut detected_major: u32 = 0 ;
874+ let mut detected_minor: u32 = 0 ;
875+ let mut path_buf = [ 0u16 ; 260 ] ;
876+ let path_len = GetModuleFileNameW ( handle, path_buf. as_mut_ptr ( ) , 260 ) ;
877+ if path_len > 0 {
878+ let mut dummy_handle: u32 = 0 ;
879+ let info_size =
880+ GetFileVersionInfoSizeW ( path_buf. as_ptr ( ) , & mut dummy_handle) ;
881+ if info_size > 0 {
882+ let mut info = vec ! [ 0u8 ; info_size as usize ] ;
883+ if GetFileVersionInfoW (
884+ path_buf. as_ptr ( ) ,
885+ 0 ,
886+ info_size,
887+ info. as_mut_ptr ( ) as * mut std:: ffi:: c_void ,
888+ ) != 0
889+ {
890+ let sub_block = "\\ \0 " . encode_utf16 ( ) . collect :: < Vec < u16 > > ( ) ;
891+ let mut vs_info: * mut std:: ffi:: c_void =
892+ std:: ptr:: null_mut ( ) ;
893+ let mut vs_len: u32 = 0 ;
894+ if VerQueryValueW (
895+ info. as_mut_ptr ( ) as * mut std:: ffi:: c_void ,
896+ sub_block. as_ptr ( ) ,
897+ & mut vs_info,
898+ & mut vs_len,
899+ ) != 0
900+ && !vs_info. is_null ( )
901+ {
902+ let fixed = vs_info as * const VS_FIXEDFILEINFO ;
903+ detected_major = ( * fixed) . dw_file_version_ms >> 16 ;
904+ detected_minor =
905+ ( * fixed) . dw_file_version_ms & 0xFFFF ;
906+ }
907+ }
908+ }
909+ }
910+
911+ FreeLibrary ( handle) ;
912+
913+ // Version compatibility check (mirrors the Linux/macOS path).
914+ // If version could not be detected (detected_major == 0) we let
915+ // the load succeed — the ort crate will diagnose further.
916+ if detected_major != 0
917+ && ( detected_major != 1 || detected_minor < 20 )
918+ {
919+ let ver = format ! ( "{}.{}" , detected_major, detected_minor) ;
920+ return Err ( format_ort_version_mismatch ( & ver, lib_name) ) ;
921+ }
922+ }
793923 }
794924
795925 Ok ( ( ) )
@@ -839,7 +969,6 @@ fn extract_version_from_filename(name: &str) -> Option<String> {
839969 re. find ( name) . map ( |m| m. as_str ( ) . to_string ( ) )
840970}
841971
842- #[ cfg( any( test, target_os = "linux" , target_os = "macos" ) ) ]
843972fn suggest_removal_command ( lib_path : & str ) -> String {
844973 if lib_path. starts_with ( "/usr/local/lib" )
845974 || lib_path == "libonnxruntime.so"
@@ -860,7 +989,6 @@ fn suggest_removal_command(lib_path: &str) -> String {
860989/// stability — the auto-fix recommendation must always come first because
861990/// it's the only safe option, and the system-rm step must remain present
862991/// because some users prefer the system-wide cleanup path.
863- #[ cfg( any( test, target_os = "linux" , target_os = "macos" ) ) ]
864992pub ( crate ) fn format_ort_version_mismatch ( version : & str , lib_name : & str ) -> String {
865993 format ! (
866994 "ONNX Runtime version mismatch: found v{} at '{}', but AFT requires v1.20+. \
0 commit comments