Subversion Repositories userdetect2

Rev

Rev 94 | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. unit UD2_Obj;
  2.  
  3. interface
  4.  
  5. {$IF CompilerVersion >= 25.0}
  6. {$LEGACYIFEND ON}
  7. {$IFEND}
  8.  
  9. {$INCLUDE 'UserDetect2.inc'}
  10.  
  11. uses
  12.   Windows, SysUtils, Classes, IniFiles, Contnrs, Dialogs, UD2_PluginIntf,
  13.   UD2_PluginStatus, UD2_Utils, UD2_Parsing;
  14.  
  15. const
  16.   UD2_TagDescription = 'Description';
  17.  
  18. type
  19.   TUD2IdentificationEntry = class;
  20.  
  21.   TUD2Plugin = class(TObject)
  22.   protected
  23.     FDetectedIdentifications: TObjectList{<TUD2IdentificationEntry>};
  24.     FOSNotSupportedEnforced: boolean;
  25.     FPluginDLL: string;
  26.     FPluginGUIDSet: boolean;
  27.     FPluginGUID: TGUID;
  28.     FPluginName: WideString;
  29.     FPluginVendor: WideString;
  30.     FPluginVersion: WideString;
  31.     FIdentificationMethodName: WideString;
  32.     FAcceptsDynamicRequests: boolean;
  33.     FIdentificationProcedureStatusCode: UD2_STATUS;
  34.     FIdentificationProcedureStatusCodeDescribed: WideString;
  35.     FLoadingTime: Cardinal;
  36.   public
  37.     // This flag will be set if "AutoOSNotSupportedCompatibility" of the INI manifest had to be enforced/used
  38.     property OSNotSupportedEnforced: boolean read FOSNotSupportedEnforced;
  39.  
  40.     // Data read from the DLL
  41.     property PluginDLL: string read FPluginDLL;
  42.     property PluginGUIDSet: boolean read FPluginGUIDSet;
  43.     property PluginGUID: TGUID read FPluginGUID;
  44.     property PluginName: WideString read FPluginName;
  45.     property PluginVendor: WideString read FPluginVendor;
  46.     property PluginVersion: WideString read FPluginVersion;
  47.     property IdentificationMethodName: WideString read FIdentificationMethodName;
  48.     property AcceptsDynamicRequests: boolean read FAcceptsDynamicRequests;
  49.  
  50.     // ONLY contains the non-failure status code of IdentificationStringW
  51.     property IdentificationProcedureStatusCode: UD2_STATUS read FIdentificationProcedureStatusCode;
  52.     property IdentificationProcedureStatusCodeDescribed: WideString read FIdentificationProcedureStatusCodeDescribed;
  53.  
  54.     // How long did the plugin to load?
  55.     property LoadingTime: Cardinal read FLoadingTime;
  56.  
  57.     function PluginGUIDString: string;
  58.     property DetectedIdentifications: TObjectList{<TUD2IdentificationEntry>} read FDetectedIdentifications;
  59.     destructor Destroy; override;
  60.     constructor Create;
  61.     function AddIdentification(IdStr: WideString): TUD2IdentificationEntry;
  62.  
  63.     function InvokeDynamicCheck(dynamicData: WideString; AErrorOut: TStrings; var outIDs: TArrayOfString): boolean; overload;
  64.     function InvokeDynamicCheck(dynamicData: WideString; AErrorOut: TStrings): boolean; overload;
  65.     function GetDynamicRequestResult(dynamicData: WideString; AErrorOut: TStrings=nil): TArrayOfString;
  66.  
  67.     function EqualsMethodNameOrGuid(idMethodNameOrGUID: string): boolean;
  68.   end;
  69.  
  70.   TUD2IdentificationEntry = class(TObject)
  71.   private
  72.     FIdentificationString: WideString;
  73.     FPlugin: TUD2Plugin;
  74.     FDynamicDataUsed: boolean;
  75.     FDynamicData: WideString;
  76.   public
  77.     property DynamicDataUsed: boolean read FDynamicDataUsed write FDynamicDataUsed;
  78.     property DynamicData: WideString read FDynamicData write FDynamicData;
  79.     property IdentificationString: WideString read FIdentificationString;
  80.     property Plugin: TUD2Plugin read FPlugin;
  81.     function GetConditionString(MethodnameAsGUID: boolean=false): TUD2TDFCondition;
  82.     procedure GetIdNames(sl: TStrings);
  83.     constructor Create(AIdentificationString: WideString; APlugin: TUD2Plugin);
  84.   end;
  85.  
  86.   TUD2 = class(TObject)
  87.   private
  88.     {$IFDEF CHECK_FOR_SAME_PLUGIN_GUID}
  89.     FGUIDLookup: TStrings;
  90.     {$ENDIF}
  91.   protected
  92.     FLoadedPlugins: TObjectList{<TUD2Plugin>};
  93.     FIniFile: TMemIniFile;
  94.     FErrors: TStrings;
  95.     FIniFileName: string;
  96.   public
  97.     property IniFileName: string read FIniFileName;
  98.     property Errors: TStrings read FErrors;
  99.     property LoadedPlugins: TObjectList{<TUD2Plugin>} read FLoadedPlugins;
  100.     property IniFile: TMemIniFile read FIniFile;
  101.     procedure GetAllDetectedIDs(outSL: TStrings);
  102.     function FulfilsEverySubterm(conds: TUD2TDFConditionArray; slIdNames: TStrings=nil; AErrorOut: TStrings=nil): boolean; overload;
  103.     function FulfilsEverySubterm(idTerm: WideString; slIdNames: TStrings=nil; AErrorOut: TStrings=nil): boolean; overload;
  104.     function CheckTerm(idTermAndCmd: string; slIdNames: TStrings=nil; AErrorOut: TStrings=nil): TUD2CommandArray;
  105.     function FindPluginByMethodNameOrGuid(idMethodName: string): TUD2Plugin;
  106.     function GetCommandList(ShortTaskName: string; AErrorOut: TStrings=nil): TUD2CommandArray;
  107.     procedure HandlePluginDir(APluginDir, AFileMask: string);
  108.     procedure GetTaskListing(outSL: TStrings);
  109.     constructor Create(AIniFileName: string);
  110.     destructor Destroy; override;
  111.     function TaskExists(ShortTaskName: string): boolean;
  112.     function ReadMetatagString(ShortTaskName, MetatagName: string; DefaultVal: string): string;
  113.     function ReadMetatagBool(ShortTaskName, MetatagName: string; DefaultVal: string): boolean;
  114.     function GetTaskName(AShortTaskName: string): string;
  115.     class function GenericErrorLookup(grStatus: UD2_STATUS): string;
  116.   end;
  117.  
  118. implementation
  119.  
  120. uses
  121.   Math;
  122.  
  123. const
  124.   cchBufferSize = 32768;
  125.  
  126. type
  127.   TUD2PluginLoader = class(TThread)
  128.   protected
  129.     dllFile: string;
  130.     lngID: LANGID;
  131.     useDynamicData: boolean;
  132.     dynamicData: WideString;
  133.     procedure Execute; override;
  134.     function HandleDLL: boolean;
  135.   public
  136.     Plugin: TUD2Plugin;
  137.     Errors: TStringList;
  138.     ResultIdentifiers: TArrayOfString;
  139.     constructor Create(Suspended: boolean; DLL: string; alngid: LANGID; useDynamicData: boolean; dynamicData: WideString);
  140.     destructor Destroy; override;
  141.   end;
  142.  
  143. class function TUD2.GenericErrorLookup(grStatus: UD2_STATUS): string;
  144. resourcestring
  145.   LNG_STATUS_OK_UNSPECIFIED               = 'Success (Unspecified)';
  146.   LNG_STATUS_OK_SINGLELINE                = 'Success (One identifier returned)';
  147.   LNG_STATUS_OK_MULTILINE                 = 'Success (Multiple identifiers returned)';
  148.   LNG_UNKNOWN_SUCCESS                     = 'Success (Unknown status code %s)';
  149.  
  150.   LNG_STATUS_NOTAVAIL_UNSPECIFIED         = 'Not available (Unspecified)';
  151.   LNG_STATUS_NOTAVAIL_OS_NOT_SUPPORTED    = 'Not available (Operating system not supported)';
  152.   LNG_STATUS_NOTAVAIL_HW_NOT_SUPPORTED    = 'Not available (Hardware not supported)';
  153.   LNG_STATUS_NOTAVAIL_NO_ENTITIES         = 'Not available (No entities to identify)';
  154.   LNG_STATUS_NOTAVAIL_WINAPI_CALL_FAILURE = 'Not available (A Windows API call failed. Message: %s)';
  155.   LNG_STATUS_NOTAVAIL_ONLY_ACCEPT_DYNAMIC = 'Not available (Arguments required)';
  156.   LNG_STATUS_NOTAVAIL_INVALID_INPUT       = 'Not available (Plugin received invalid input)';
  157.   LNG_STATUS_NOTAVAIL_DOES_NOT_ACCEPT_DYNAMIC_REQUESTS = 'Not available (Plugin does not allow dynamic requests)';
  158.   LNG_UNKNOWN_NOTAVAIL                    = 'Not available (Unknown status code %s)';
  159.  
  160.   LNG_STATUS_FAILURE_UNSPECIFIED          = 'Error (Unspecified)';
  161.   LNG_STATUS_FAILURE_BUFFER_TOO_SMALL     = 'Error (The provided buffer is too small!)';
  162.   LNG_STATUS_FAILURE_INVALID_ARGS         = 'Error (An internal function received invalid arguments!)';
  163.   LNG_STATUS_FAILURE_PLUGIN_NOT_LICENSED  = 'Error (The plugin is not licensed)';
  164.   LNG_STATUS_FAILURE_NO_RETURNED_VALUE    = 'Error (Plugin did not return a status)';
  165.   LNG_STATUS_FAILURE_CATCHED_EXCEPTION    = 'Error (Catched unexpected Exception)';
  166.   LNG_UNKNOWN_FAILED                      = 'Error (Unknown status code %s)';
  167.  
  168.   LNG_UNKNOWN_STATUS                      = 'Unknown status code with unexpected category: %s';
  169. begin
  170.        if UD2_STATUS_Equal(grStatus, UD2_STATUS_OK_UNSPECIFIED, false)               then result := LNG_STATUS_OK_UNSPECIFIED
  171.   else if UD2_STATUS_Equal(grStatus, UD2_STATUS_OK_SINGLELINE, false)                then result := LNG_STATUS_OK_SINGLELINE
  172.   else if UD2_STATUS_Equal(grStatus, UD2_STATUS_OK_MULTILINE, false)                 then result := LNG_STATUS_OK_MULTILINE
  173.  
  174.   else if UD2_STATUS_Equal(grStatus, UD2_STATUS_NOTAVAIL_UNSPECIFIED, false)         then result := LNG_STATUS_NOTAVAIL_UNSPECIFIED
  175.   else if UD2_STATUS_Equal(grStatus, UD2_STATUS_NOTAVAIL_OS_NOT_SUPPORTED, false)    then result := LNG_STATUS_NOTAVAIL_OS_NOT_SUPPORTED
  176.   else if UD2_STATUS_Equal(grStatus, UD2_STATUS_NOTAVAIL_HW_NOT_SUPPORTED, false)    then result := LNG_STATUS_NOTAVAIL_HW_NOT_SUPPORTED
  177.   else if UD2_STATUS_Equal(grStatus, UD2_STATUS_NOTAVAIL_NO_ENTITIES, false)         then result := LNG_STATUS_NOTAVAIL_NO_ENTITIES
  178.   else if UD2_STATUS_Equal(grStatus, UD2_STATUS_NOTAVAIL_WINAPI_CALL_FAILURE, false) then result := Format(LNG_STATUS_NOTAVAIL_WINAPI_CALL_FAILURE, [FormatOSError(grStatus.dwExtraInfo)])
  179.   else if UD2_STATUS_Equal(grStatus, UD2_STATUS_NOTAVAIL_ONLY_ACCEPT_DYNAMIC, false) then result := LNG_STATUS_NOTAVAIL_ONLY_ACCEPT_DYNAMIC
  180.   else if UD2_STATUS_Equal(grStatus, UD2_STATUS_NOTAVAIL_INVALID_INPUT, false)       then result := LNG_STATUS_NOTAVAIL_INVALID_INPUT
  181.   else if UD2_STATUS_Equal(grStatus, UD2_STATUS_NOTAVAIL_DOES_NOT_ACCEPT_DYNAMIC_REQUESTS, false) then result := LNG_STATUS_NOTAVAIL_DOES_NOT_ACCEPT_DYNAMIC_REQUESTS
  182.  
  183.   else if UD2_STATUS_Equal(grStatus, UD2_STATUS_FAILURE_UNSPECIFIED, false)          then result := LNG_STATUS_FAILURE_UNSPECIFIED
  184.   else if UD2_STATUS_Equal(grStatus, UD2_STATUS_FAILURE_BUFFER_TOO_SMALL, false)     then result := LNG_STATUS_FAILURE_BUFFER_TOO_SMALL
  185.   else if UD2_STATUS_Equal(grStatus, UD2_STATUS_FAILURE_INVALID_ARGS, false)         then result := LNG_STATUS_FAILURE_INVALID_ARGS
  186.   else if UD2_STATUS_Equal(grStatus, UD2_STATUS_FAILURE_PLUGIN_NOT_LICENSED, false)  then result := LNG_STATUS_FAILURE_PLUGIN_NOT_LICENSED
  187.   else if UD2_STATUS_Equal(grStatus, UD2_STATUS_FAILURE_NO_RETURNED_VALUE, false)    then result := LNG_STATUS_FAILURE_NO_RETURNED_VALUE
  188.   else if UD2_STATUS_Equal(grStatus, UD2_STATUS_FAILURE_CATCHED_EXCEPTION, false)    then result := LNG_STATUS_FAILURE_CATCHED_EXCEPTION
  189.  
  190.   else if grStatus.wCategory = UD2_STATUSCAT_SUCCESS   then result := Format(LNG_UNKNOWN_SUCCESS,  [UD2_STATUS_FormatStatusCode(grStatus)])
  191.   else if grStatus.wCategory = UD2_STATUSCAT_NOT_AVAIL then result := Format(LNG_UNKNOWN_NOTAVAIL, [UD2_STATUS_FormatStatusCode(grStatus)])
  192.   else if grStatus.wCategory = UD2_STATUSCAT_FAILED    then result := Format(LNG_UNKNOWN_FAILED,   [UD2_STATUS_FormatStatusCode(grStatus)])
  193.   else                                                      result := Format(LNG_UNKNOWN_STATUS,   [UD2_STATUS_FormatStatusCode(grStatus)]);
  194. end;
  195.  
  196. { TUD2Plugin }
  197.  
  198. function TUD2Plugin.PluginGUIDString: string;
  199. begin
  200.   if PluginGUIDSet then
  201.     result := UpperCase(GUIDToString(PluginGUID))
  202.   else
  203.     result := '';
  204. end;
  205.  
  206. function TUD2Plugin.AddIdentification(IdStr: WideString): TUD2IdentificationEntry;
  207. begin
  208.   result := TUD2IdentificationEntry.Create(IdStr, Self);
  209.   DetectedIdentifications.Add(result);
  210. end;
  211.  
  212. destructor TUD2Plugin.Destroy;
  213. begin
  214.   DetectedIdentifications.Free;
  215.   inherited;
  216. end;
  217.  
  218. constructor TUD2Plugin.Create;
  219. begin
  220.   inherited Create;
  221.   FDetectedIdentifications := TObjectList{<TUD2IdentificationEntry>}.Create(true);
  222. end;
  223.  
  224. function TUD2Plugin.InvokeDynamicCheck(dynamicData: WideString; AErrorOut: TStrings; var outIDs: TArrayOfString): boolean;
  225. var
  226.   ude: TUD2IdentificationEntry;
  227.   i: integer;
  228.   id: string;
  229.   l: integer;
  230. begin
  231.   result := false;
  232.  
  233.   SetLength(outIDs, 0);
  234.  
  235.   for i := 0 to FDetectedIdentifications.Count-1 do
  236.   begin
  237.     ude := FDetectedIdentifications.Items[i] as TUD2IdentificationEntry;
  238.     if ude.dynamicDataUsed and (ude.dynamicData = dynamicData) then
  239.     begin
  240.       l := Length(outIDs);
  241.       SetLength(outIDs, l+1);
  242.       outIDs[l] := ude.FIdentificationString;
  243.     end;
  244.   end;
  245.  
  246.   // The dynamic content was already evaluated (and therefore is already added in FDetectedIdentifications).
  247.   if Length(outIDs) > 0 then exit;
  248.  
  249.   outIDs := GetDynamicRequestResult(dynamicData, AErrorOut);
  250.  
  251.   for i := 0 to Length(outIDs)-1 do
  252.   begin
  253.     id := outIDs[i];
  254.  
  255.     ude := AddIdentification(id);
  256.     ude.dynamicDataUsed := true;
  257.     ude.dynamicData := dynamicData;
  258.  
  259.     result := true;
  260.   end;
  261. end;
  262.  
  263. function TUD2Plugin.GetDynamicRequestResult(dynamicData: WideString; AErrorOut: TStrings=nil): TArrayOfString;
  264. var
  265.   lngID: LANGID;
  266.   loader: TUD2PluginLoader;
  267. begin
  268.   lngID := GetSystemDefaultLangID;
  269.  
  270.   loader := TUD2PluginLoader.Create(false, PluginDLL, lngid, true, dynamicData);
  271.   try
  272.     loader.WaitFor;
  273.     result := loader.ResultIdentifiers;
  274.     if Assigned(AErrorOut) then
  275.     begin
  276.       AErrorOut.AddStrings(loader.Errors);
  277.     end;
  278.  
  279.     // TODO: Use assign() instead? or allow TUD2PluginLoader to write the TPlugin object directly?
  280.     //       Should we even overwrite the current plugin data, or return the new plugin?
  281.     FIdentificationProcedureStatusCode := loader.plugin.IdentificationProcedureStatusCode;
  282.     FIdentificationProcedureStatusCodeDescribed := loader.plugin.IdentificationProcedureStatusCodeDescribed;
  283.     FOSNotSupportedEnforced := loader.plugin.OSNotSupportedEnforced;
  284.     FLoadingTime := loader.Plugin.LoadingTime;
  285.  
  286.   finally
  287.     if Assigned(loader.Plugin) then FreeAndNil(loader.Plugin);
  288.     loader.Free;
  289.   end;
  290. end;
  291.  
  292. function TUD2Plugin.EqualsMethodNameOrGuid(idMethodNameOrGUID: string): boolean;
  293. begin
  294.   result := SameText(IdentificationMethodName, idMethodNameOrGUID) or
  295.             SameText(GUIDToString(PluginGUID), idMethodNameOrGUID)
  296. end;
  297.  
  298. function TUD2Plugin.InvokeDynamicCheck(dynamicData: WideString; AErrorOut: TStrings): boolean;
  299. var
  300.   dummy: TArrayOfString;
  301. begin
  302.   result := InvokeDynamicCheck(dynamicData, AErrorOut, dummy)
  303. end;
  304.  
  305. { TUD2IdentificationEntry }
  306.  
  307. procedure TUD2IdentificationEntry.GetIdNames(sl: TStrings);
  308. var
  309.   cond: TUD2TDFCondition;
  310. begin
  311.   cond.idMethodName := Plugin.IdentificationMethodName;
  312.   cond.idStr := IdentificationString;
  313.   cond.dynamicDataUsed := DynamicDataUsed;
  314.   cond.dynamicData := DynamicData;
  315.   sl.Add(UD2_CondToStr(cond));
  316.  
  317.   cond.idMethodName := Plugin.PluginGUIDString;
  318.   sl.Add(UD2_CondToStr(cond));
  319. end;
  320.  
  321. constructor TUD2IdentificationEntry.Create(AIdentificationString: WideString;
  322.   APlugin: TUD2Plugin);
  323. begin
  324.   inherited Create;
  325.  
  326.   // TODO: We need to do this, because ReadSectionValues strips the names of the name-value pairs...
  327.   //       We should correct ReadSectionValues...
  328.   // Example: DriveSerial(c:):2SHSWNHA010807 X    =calc.exe
  329.   // ReadSectionValues will return "DriveSerial(c:):2SHSWNHA010807 X=calc.exe"
  330.   AIdentificationString := Trim(AIdentificationString);
  331.  
  332.   FIdentificationString := AIdentificationString;
  333.   FPlugin := APlugin;
  334. end;
  335.  
  336. function TUD2IdentificationEntry.GetConditionString(MethodnameAsGUID: boolean=false): TUD2TDFCondition;
  337. begin
  338.   if MethodnameAsGUID then
  339.     Result.idMethodName := GUIDToString(Self.Plugin.PluginGUID)
  340.   else
  341.     Result.idMethodName := Self.Plugin.IdentificationMethodName;
  342.  
  343.   Result.idStr := Self.IdentificationString;
  344.   Result.dynamicDataUsed := Self.DynamicDataUsed;
  345.   Result.dynamicData := Self.DynamicData;
  346.   Result.caseSensitive := false;
  347. end;
  348.  
  349. { TUD2 }
  350.  
  351. procedure TUD2.HandlePluginDir(APluginDir, AFileMask: string);
  352. Var
  353.   SR: TSearchRec;
  354.   path: string;
  355.   pluginLoader: TUD2PluginLoader;
  356.   tob: TObjectList{<TUD2PluginLoader>};
  357.   i: integer;
  358.   {$IFDEF CHECK_FOR_SAME_PLUGIN_GUID}
  359.   sPluginID, prevDLL: string;
  360.   {$ENDIF}
  361.   lngid: LANGID;
  362. resourcestring
  363.   LNG_PLUGINS_SAME_GUID = 'Attention: The plugin "%s" and the plugin "%s" have the same identification GUID. The latter will not be loaded.';
  364. begin
  365.   tob := TObjectList{<TUD2PluginLoader>}.Create;
  366.   try
  367.     tob.OwnsObjects := false;
  368.  
  369.     lngID := GetSystemDefaultLangID;
  370.  
  371.     path := APluginDir;
  372.     if path <> '' then path := IncludeTrailingPathDelimiter(path);
  373.  
  374.     if FindFirst(path + AFileMask, 0, SR) = 0 then
  375.     begin
  376.       try
  377.         repeat
  378.           try
  379.             tob.Add(TUD2PluginLoader.Create(false, path + sr.Name, lngid, false, ''));
  380.           except
  381.             on E: Exception do
  382.             begin
  383.               MessageDlg(E.Message, mtError, [mbOK], 0);
  384.             end;
  385.           end;
  386.         until FindNext(SR) <> 0;
  387.       finally
  388.         FindClose(SR);
  389.       end;
  390.     end;
  391.  
  392.     for i := 0 to tob.count-1 do
  393.     begin
  394.       pluginLoader := tob.items[i] as TUD2PluginLoader;
  395.       pluginLoader.WaitFor;
  396.       Errors.AddStrings(pluginLoader.Errors);
  397.       if Assigned(pluginLoader.Plugin) then
  398.       begin
  399.         {$IFDEF CHECK_FOR_SAME_PLUGIN_GUID}
  400.         if pluginLoader.Plugin.PluginGUIDSet then
  401.         begin
  402.           sPluginID := GUIDToString(pluginLoader.Plugin.PluginGUID);
  403.           prevDLL := FGUIDLookup.Values[sPluginID];
  404.           if (prevDLL <> '') and (prevDLL <> pluginLoader.Plugin.PluginDLL) then
  405.           begin
  406.             Errors.Add(Format(LNG_PLUGINS_SAME_GUID, [prevDLL, pluginLoader.Plugin.PluginDLL]));
  407.             pluginLoader.Plugin.Free;
  408.           end
  409.           else
  410.           begin
  411.             FGUIDLookup.Values[sPluginID] := pluginLoader.Plugin.PluginDLL;
  412.             LoadedPlugins.Add(pluginLoader.Plugin);
  413.           end;
  414.         end
  415.         else
  416.         begin
  417.           LoadedPlugins.Add(pluginLoader.Plugin);
  418.         end;
  419.         {$ELSE}
  420.         LoadedPlugins.Add(pluginLoader.Plugin);
  421.         {$ENDIF}
  422.       end;
  423.       pluginLoader.Free;
  424.     end;
  425.   finally
  426.     tob.free;
  427.   end;
  428. end;
  429.  
  430. destructor TUD2.Destroy;
  431. begin
  432.   FIniFile.Free;
  433.   FLoadedPlugins.Free;
  434.   {$IFDEF CHECK_FOR_SAME_PLUGIN_GUID}
  435.   FGUIDLookup.Free;
  436.   {$ENDIF}
  437.   FErrors.Free;
  438. end;
  439.  
  440. constructor TUD2.Create(AIniFileName: string);
  441. begin
  442.   FIniFileName := AIniFileName;
  443.   FLoadedPlugins := TObjectList{<TUD2Plugin>}.Create(true);
  444.   FIniFile := TMemIniFile.Create(IniFileName);
  445.   {$IFDEF CHECK_FOR_SAME_PLUGIN_GUID}
  446.   FGUIDLookup := TStringList.Create;
  447.   {$ENDIF}
  448.   FErrors := TStringList.Create;
  449. end;
  450.  
  451. function TUD2.GetTaskName(AShortTaskName: string): string;
  452. resourcestring
  453.   LNG_NO_DESCRIPTION = '(%s)';
  454. begin
  455.   result := FIniFile.ReadString(AShortTaskName, UD2_TagDescription, Format(LNG_NO_DESCRIPTION, [AShortTaskName]));
  456. end;
  457.  
  458. procedure TUD2.GetTaskListing(outSL: TStrings);
  459. var
  460.   sl: TStringList;
  461.   i: integer;
  462.   desc: string;
  463. begin
  464.   sl := TStringList.Create;
  465.   try
  466.     FIniFile.ReadSections(sl);
  467.     for i := 0 to sl.Count-1 do
  468.     begin
  469.       desc := GetTaskName(sl.Strings[i]);
  470.       outSL.Values[sl.Strings[i]] := desc;
  471.     end;
  472.   finally
  473.     sl.Free;
  474.   end;
  475. end;
  476.  
  477. function TUD2.TaskExists(ShortTaskName: string): boolean;
  478. begin
  479.   result := FIniFile.SectionExists(ShortTaskName);
  480. end;
  481.  
  482. function TUD2.ReadMetatagString(ShortTaskName, MetatagName: string; DefaultVal: string): string;
  483. begin
  484.   result := IniFile.ReadString(ShortTaskName, MetatagName, DefaultVal);
  485. end;
  486.  
  487. function TUD2.ReadMetatagBool(ShortTaskName, MetatagName: string; DefaultVal: string): boolean;
  488. begin
  489.   // DefaultVal is a string, because we want to allow an empty string, in case the
  490.   // user wishes an Exception in case the string is not a valid boolean string
  491.   result := BetterInterpreteBool(IniFile.ReadString(ShortTaskName, MetatagName, DefaultVal));
  492. end;
  493.  
  494. (*
  495.  
  496. NAMING EXAMPLE: $CASESENSITIVE$ComputerName(dynXYZ):ABC&&User:John=calc.exe$RIOD$
  497.  
  498.         idTerm:       ComputerName(dynXYZ):ABC&&User:John
  499.         idName:       ComputerName:ABC
  500.         IdMethodName: ComputerName
  501.         IdStr         ABC
  502.         cmd:          calc.exe
  503.         dynamicData:  dynXYZ
  504.  
  505. *)
  506.  
  507. procedure TUD2.GetAllDetectedIDs(outSL: TStrings);
  508. var
  509.   i, j: integer;
  510.   pl: TUD2Plugin;
  511.   ude: TUD2IdentificationEntry;
  512. begin
  513.   for i := 0 to LoadedPlugins.Count-1 do
  514.   begin
  515.     pl := LoadedPlugins.Items[i] as TUD2Plugin;
  516.     for j := 0 to pl.DetectedIdentifications.Count-1 do
  517.     begin
  518.       ude := pl.DetectedIdentifications.Items[j] as TUD2IdentificationEntry;
  519.       ude.GetIdNames(outSL);
  520.     end;
  521.   end;
  522. end;
  523.  
  524. function TUD2.FulfilsEverySubterm(conds: TUD2TDFConditionArray; slIdNames: TStrings=nil; AErrorOut: TStrings=nil): boolean;
  525. begin
  526.   result := FulfilsEverySubterm(UD2_CondsToStr(conds), slIdNames, AErrorOut);
  527. end;
  528.  
  529. function TUD2.FulfilsEverySubterm(idTerm: WideString; slIdNames: TStrings=nil; AErrorOut: TStrings=nil): boolean;
  530. var
  531.   i: integer;
  532.   p: TUD2Plugin;
  533.   cleanUpStringList: boolean;
  534.   conds: TUD2TDFConditionArray;
  535.   cond: TUD2TDFCondition;
  536.   idName: string;
  537. begin
  538.   {$IFDEF NO_CONDITIONS_IS_FAILURE}
  539.   if idTerm = '' then
  540.   begin
  541.     SetLength(conds, 0);
  542.     result := false;
  543.     Exit;
  544.   end;
  545.   {$ENDIF}
  546.  
  547.   cleanUpStringList := slIdNames = nil;
  548.   try
  549.     if cleanUpStringList then
  550.     begin
  551.       slIdNames := TStringList.Create;
  552.       GetAllDetectedIDs(slIdNames);
  553.     end;
  554.  
  555.     conds := UD2P_ParseConditions(idTerm);
  556.  
  557.     result := true;
  558.     for i := Low(conds) to High(conds) do
  559.     begin
  560.       cond := conds[i];
  561.  
  562.       if cond.dynamicDataUsed then
  563.       begin
  564.         p := FindPluginByMethodNameOrGuid(cond.idMethodName);
  565.         if Assigned(p) then
  566.         begin
  567.           if p.InvokeDynamicCheck(cond.dynamicData, AErrorOut) then
  568.           begin
  569.             // Reload the identifications
  570.             slIdNames.Clear;
  571.             GetAllDetectedIDs(slIdNames);
  572.           end;
  573.         end;
  574.       end;
  575.  
  576.       idName := UD2_CondToStr(cond);
  577.  
  578.       if (not cond.caseSensitive and (slIdNames.IndexOf(idName) = -1)) or
  579.          (cond.caseSensitive and (IndexOf_CS(slIdNames, idName) = -1)) then
  580.       begin
  581.         result := false;
  582.         break;
  583.       end;
  584.     end;
  585.   finally
  586.     if cleanUpStringList and Assigned(slIdNames) then
  587.       slIdNames.Free;
  588.   end;
  589. end;
  590.  
  591. function TUD2.FindPluginByMethodNameOrGuid(idMethodName: string): TUD2Plugin;
  592. var
  593.   i: integer;
  594.   p: TUD2Plugin;
  595. begin
  596.   result := nil;
  597.   for i := 0 to LoadedPlugins.Count-1 do
  598.   begin
  599.     p := LoadedPlugins.Items[i] as TUD2Plugin;
  600.  
  601.     if p.EqualsMethodNameOrGuid(idMethodName) then
  602.     begin
  603.       result := p;
  604.       Exit;
  605.     end;
  606.   end;
  607. end;
  608.  
  609. function TUD2.GetCommandList(ShortTaskName: string; AErrorOut: TStrings=nil): TUD2CommandArray;
  610. var
  611.   i, j, l: integer;
  612.   slSV, slIdNames: TStrings;
  613.   tmpCmds: TUD2CommandArray;
  614. begin
  615.   SetLength(result, 0);
  616.   SetLength(tmpCmds, 0);
  617.  
  618.   slIdNames := TStringList.Create;
  619.   try
  620.     GetAllDetectedIDs(slIdNames);
  621.  
  622.     slSV := TStringList.Create;
  623.     try
  624.       FIniFile.ReadSectionValues(ShortTaskName, slSV);
  625.       for i := 0 to slSV.Count-1 do
  626.       begin
  627.         tmpCmds := CheckTerm(slSV.Strings[i], slIdNames, AErrorOut);
  628.         for j := Low(tmpCmds) to High(tmpCmds) do
  629.         begin
  630.           l := Length(result);
  631.           SetLength(result, l+1);
  632.           result[l] := tmpCmds[j];
  633.         end;
  634.       end;
  635.     finally
  636.       slSV.Free;
  637.     end;
  638.   finally
  639.     slIdNames.Free;
  640.   end;
  641. end;
  642.  
  643. function TUD2.CheckTerm(idTermAndCmd: string; slIdNames: TStrings=nil; AErrorOut: TStrings=nil): TUD2CommandArray;
  644. var
  645.   slIdNamesCreated: boolean;
  646.   ent: TUD2TDFEntry;
  647. begin
  648.   SetLength(result, 0);
  649.  
  650.   slIdNamesCreated := false;
  651.   try
  652.     if not Assigned(slIdNames) then
  653.     begin
  654.       slIdNamesCreated := true;
  655.       slIdNames := TStringList.Create;
  656.       GetAllDetectedIDs(slIdNames);
  657.     end;
  658.  
  659.     if not UD2P_ParseTdfLine(idTermAndCmd, ent) then Exit;
  660.     if FulfilsEverySubterm(ent.ids, slIdNames, AErrorOut) then
  661.     begin
  662.       result := ent.commands;
  663.     end;
  664.   finally
  665.     if slIdNamesCreated then slIdNames.Free;
  666.   end;
  667. end;
  668.  
  669. { TUD2PluginLoader }
  670.  
  671. procedure TUD2PluginLoader.Execute;
  672. begin
  673.   inherited;
  674.  
  675.   HandleDLL;
  676. end;
  677.  
  678. constructor TUD2PluginLoader.Create(Suspended: boolean; DLL: string; alngid: LANGID; useDynamicData: boolean; dynamicData: WideString);
  679. begin
  680.   inherited Create(Suspended);
  681.   dllfile := dll;
  682.   Plugin := nil;
  683.   Errors := TStringList.Create;
  684.   lngid := alngid;
  685.   self.useDynamicData := useDynamicData;
  686.   Self.dynamicData := dynamicData;
  687. end;
  688.  
  689. destructor TUD2PluginLoader.Destroy;
  690. begin
  691.   Errors.Free;
  692.   inherited;
  693. end;
  694.  
  695. function TUD2PluginLoader.HandleDLL: boolean;
  696. var
  697.   sIdentifier: WideString;
  698.   buf: array[0..cchBufferSize-1] of WideChar;
  699.   pluginInterfaceID: TGUID;
  700.   dllHandle: Cardinal;
  701.   fPluginInterfaceID: TFuncPluginInterfaceID;
  702.   fPluginIdentifier: TFuncPluginIdentifier;
  703.   fPluginNameW: TFuncPluginNameW;
  704.   fPluginVendorW: TFuncPluginVendorW;
  705.   fPluginVersionW: TFuncPluginVersionW;
  706.   fIdentificationMethodNameW: TFuncIdentificationMethodNameW;
  707.   fIdentificationStringW: TFuncIdentificationStringW;
  708.   fDynamicIdentificationStringW: TFuncDynamicIdentificationStringW;
  709.   fCheckLicense: TFuncCheckLicense;
  710.   fDescribeOwnStatusCodeW: TFuncDescribeOwnStatusCodeW;
  711.   statusCode: UD2_STATUS;
  712.   i: integer;
  713.   starttime, endtime, time: cardinal;
  714.   bakErrorMode: DWORD;
  715.   err: DWORD;
  716.  
  717.   function _ErrorLookup(statusCode: UD2_STATUS): WideString;
  718.   var
  719.     ret: BOOL;
  720.     buf: array[0..cchBufferSize-1] of WideChar;
  721.   begin
  722.     if Assigned(fDescribeOwnStatusCodeW) then
  723.     begin
  724.       ZeroMemory(@buf, cchBufferSize*SizeOf(WideChar));
  725.       ret := fDescribeOwnStatusCodeW(@buf, cchBufferSize, statusCode, lngID);
  726.       if ret then
  727.       begin
  728.         result := PWideChar(@buf);
  729.         Exit;
  730.       end;
  731.     end;
  732.     result := TUD2.GenericErrorLookup(statusCode);
  733.   end;
  734.  
  735.   function _ApplyCompatibilityGUID: boolean;
  736.   var
  737.     iniConfig: TIniFile;
  738.     sOverrideGUID: string;
  739.     sPluginConfigFile: string;
  740.   begin
  741.     result := false;
  742.     sPluginConfigFile := ChangeFileExt(dllFile, '.ini');
  743.     if FileExists(sPluginConfigFile) then
  744.     begin
  745.       iniConfig := TIniFile.Create(sPluginConfigFile);
  746.       try
  747.         sOverrideGUID := iniConfig.ReadString('Compatibility', 'OverrideGUID', '');
  748.         if sOverrideGUID <> '' then
  749.         begin
  750.           Plugin.FPluginGUIDSet := true;
  751.           Plugin.FPluginGUID := StringToGUID(sOverrideGUID);
  752.           result := true;
  753.         end;
  754.       finally
  755.         iniConfig.Free;
  756.       end;
  757.     end;
  758.   end;
  759.  
  760.   function _AutoOSNotSupportedMode: integer;
  761.   var
  762.     iniConfig: TIniFile;
  763.     sPluginConfigFile: string;
  764.   begin
  765.     result := 0;
  766.     sPluginConfigFile := ChangeFileExt(dllFile, '.ini');
  767.     if FileExists(sPluginConfigFile) then
  768.     begin
  769.       iniConfig := TIniFile.Create(sPluginConfigFile);
  770.       try
  771.         result := iniConfig.ReadInteger('Compatibility', 'AutoOSNotSupported', 0);
  772.       finally
  773.         iniConfig.Free;
  774.       end;
  775.     end;
  776.   end;
  777.  
  778.   procedure _OverwriteStatusToOSNotSupported;
  779.   begin
  780.     Plugin := TUD2Plugin.Create;
  781.     Plugin.FPluginDLL := dllFile;
  782.     statusCode := UD2_STATUS_NOTAVAIL_OS_NOT_SUPPORTED;
  783.     Plugin.FIdentificationProcedureStatusCode := statusCode;
  784.     Plugin.FIdentificationProcedureStatusCodeDescribed := _ErrorLookup(statusCode);
  785.     Plugin.FOSNotSupportedEnforced := true;
  786.     result := true;
  787.   end;
  788.  
  789. resourcestring
  790.   LNG_DLL_NOT_LOADED = 'Plugin DLL "%s" could not be loaded: %s';
  791.   LNG_METHOD_NOT_FOUND = 'Method "%s" not found in plugin "%s". The DLL is probably not a valid plugin DLL.';
  792.   LNG_INVALID_PLUGIN = 'The plugin "%s" is not a valid plugin for this application.';
  793.   LNG_METHOD_FAILURE = 'Error "%s" at method "%s" of plugin "%s".';
  794.   LNG_EXCEPTION = 'Fatal error while loading "%s" (%s: %s)';
  795. begin
  796.   result := false;
  797.   startTime := GetTickCount;
  798.  
  799.   try
  800.     bakErrorMode := 0;
  801.     UD2_SetThreadErrorMode(SEM_FAILCRITICALERRORS, Pointer(bakErrorMode));
  802.     try
  803.       dllHandle := LoadLibrary(PChar(dllFile));
  804.       if dllHandle = 0 then
  805.       begin
  806.         err := GetLastError;
  807.  
  808.         if ((_AutoOSNotSupportedMode = 1) and ((err = ERROR_DLL_NOT_FOUND) or (err = ERROR_PROC_NOT_FOUND))) or
  809.            (_AutoOSNotSupportedMode >= 2) then
  810.         begin
  811.           _OverwriteStatusToOSNotSupported;
  812.           Exit;
  813.         end;
  814.  
  815.         Errors.Add(Format(LNG_DLL_NOT_LOADED, [dllFile, SysErrorMessage(err)]));
  816.         Exit;
  817.       end;
  818.       try
  819.         @fPluginInterfaceID := GetProcAddress(dllHandle, mnPluginInterfaceID);
  820.         if not Assigned(fPluginInterfaceID) then
  821.         begin
  822.           Errors.Add(Format(LNG_METHOD_NOT_FOUND, [mnPluginInterfaceID, dllFile]));
  823.           Exit;
  824.         end;
  825.         pluginInterfaceID := fPluginInterfaceID();
  826.         if not IsEqualGUID(pluginInterfaceID, GUID_USERDETECT2_IDPLUGIN_V1) then
  827.         begin
  828.           Errors.Add(Format(LNG_INVALID_PLUGIN, [dllFile]));
  829.           Exit;
  830.         end;
  831.  
  832.         Plugin := TUD2Plugin.Create;
  833.         Plugin.FPluginDLL := dllFile;
  834.  
  835.         @fDynamicIdentificationStringW := GetProcAddress(dllHandle, mnDynamicIdentificationStringW);
  836.         Plugin.FAcceptsDynamicRequests := Assigned(fDynamicIdentificationStringW);
  837.  
  838.         fIdentificationStringW := nil;
  839.         if useDynamicData then
  840.         begin
  841.           if not Plugin.AcceptsDynamicRequests then
  842.           begin
  843.             // We should not output a fatal error here, because it is likely that the error is caused by the user writing a buggy INI file (specifying a parameter for a non-dynamic plugin)
  844.             (*
  845.             Errors.Add(Format(LNG_METHOD_NOT_FOUND, [mnDynamicIdentificationStringW, dllFile]));
  846.             Exit;
  847.             *)
  848.  
  849.             // But we just try to find out if the plugin seems to be "OK"
  850.             if not Assigned(fIdentificationStringW) then
  851.             begin
  852.               Errors.Add(Format(LNG_METHOD_NOT_FOUND, [mnDynamicIdentificationStringW, dllFile]));
  853.               Exit;
  854.             end;
  855.  
  856.             Plugin.FIdentificationProcedureStatusCode := UD2_STATUS_NOTAVAIL_DOES_NOT_ACCEPT_DYNAMIC_REQUESTS;
  857.             Plugin.FIdentificationProcedureStatusCodeDescribed := _ErrorLookup(statusCode);
  858.  
  859.             Exit;
  860.           end;
  861.         end
  862.         else
  863.         begin
  864.           @fIdentificationStringW := GetProcAddress(dllHandle, mnIdentificationStringW);
  865.           if not Assigned(fIdentificationStringW) then
  866.           begin
  867.             Errors.Add(Format(LNG_METHOD_NOT_FOUND, [mnIdentificationStringW, dllFile]));
  868.             Exit;
  869.           end;
  870.         end;
  871.  
  872.         @fPluginNameW := GetProcAddress(dllHandle, mnPluginNameW);
  873.         if not Assigned(fPluginNameW) then
  874.         begin
  875.           Errors.Add(Format(LNG_METHOD_NOT_FOUND, [mnPluginNameW, dllFile]));
  876.           Exit;
  877.         end;
  878.  
  879.         @fPluginVendorW := GetProcAddress(dllHandle, mnPluginVendorW);
  880.         if not Assigned(fPluginVendorW) then
  881.         begin
  882.           Errors.Add(Format(LNG_METHOD_NOT_FOUND, [mnPluginVendorW, dllFile]));
  883.           Exit;
  884.         end;
  885.  
  886.         @fPluginVersionW := GetProcAddress(dllHandle, mnPluginVersionW);
  887.         if not Assigned(fPluginVersionW) then
  888.         begin
  889.           Errors.Add(Format(LNG_METHOD_NOT_FOUND, [mnPluginVersionW, dllFile]));
  890.           Exit;
  891.         end;
  892.  
  893.         @fCheckLicense := GetProcAddress(dllHandle, mnCheckLicense);
  894.         if not Assigned(fCheckLicense) then
  895.         begin
  896.           Errors.Add(Format(LNG_METHOD_NOT_FOUND, [mnCheckLicense, dllFile]));
  897.           Exit;
  898.         end;
  899.  
  900.         @fIdentificationMethodNameW := GetProcAddress(dllHandle, mnIdentificationMethodNameW);
  901.         if not Assigned(fIdentificationMethodNameW) then
  902.         begin
  903.           Errors.Add(Format(LNG_METHOD_NOT_FOUND, [mnIdentificationMethodNameW, dllFile]));
  904.           Exit;
  905.         end;
  906.  
  907.         @fDescribeOwnStatusCodeW := GetProcAddress(dllHandle, mnDescribeOwnStatusCodeW);
  908.         if not Assigned(fDescribeOwnStatusCodeW) then
  909.         begin
  910.           Errors.Add(Format(LNG_METHOD_NOT_FOUND, [mnDescribeOwnStatusCodeW, dllFile]));
  911.           Exit;
  912.         end;
  913.  
  914.         if not _ApplyCompatibilityGUID then
  915.         begin
  916.           @fPluginIdentifier := GetProcAddress(dllHandle, mnPluginIdentifier);
  917.           if not Assigned(fPluginIdentifier) then
  918.           begin
  919.             Errors.Add(Format(LNG_METHOD_NOT_FOUND, [mnPluginIdentifier, dllFile]));
  920.             Exit;
  921.           end;
  922.           Plugin.FPluginGUIDSet := true;
  923.           Plugin.FPluginGUID := fPluginIdentifier();
  924.         end;
  925.  
  926.         statusCode := fCheckLicense(nil);
  927.         if statusCode.wCategory = UD2_STATUSCAT_FAILED then
  928.         begin
  929.           Errors.Add(Format(LNG_METHOD_FAILURE, [_ErrorLookup(statusCode), mnCheckLicense, dllFile]));
  930.           Exit;
  931.         end;
  932.  
  933.         ZeroMemory(@buf, cchBufferSize*SizeOf(WideChar));
  934.         statusCode := fPluginNameW(@buf, cchBufferSize, lngID);
  935.              if statusCode.wCategory = UD2_STATUSCAT_SUCCESS   then Plugin.FPluginName := PWideChar(@buf)
  936.         else if statusCode.wCategory = UD2_STATUSCAT_NOT_AVAIL then Plugin.FPluginName := ''
  937.         else
  938.         begin
  939.           Errors.Add(Format(LNG_METHOD_FAILURE, [_ErrorLookup(statusCode), mnPluginNameW, dllFile]));
  940.           Exit;
  941.         end;
  942.  
  943.         ZeroMemory(@buf, cchBufferSize*SizeOf(WideChar));
  944.         statusCode := fPluginVendorW(@buf, cchBufferSize, lngID);
  945.              if statusCode.wCategory = UD2_STATUSCAT_SUCCESS   then Plugin.FPluginVendor := PWideChar(@buf)
  946.         else if statusCode.wCategory = UD2_STATUSCAT_NOT_AVAIL then Plugin.FPluginVendor := ''
  947.         else
  948.         begin
  949.           Errors.Add(Format(LNG_METHOD_FAILURE, [_ErrorLookup(statusCode), mnPluginVendorW, dllFile]));
  950.           Exit;
  951.         end;
  952.  
  953.         ZeroMemory(@buf, cchBufferSize*SizeOf(WideChar));
  954.         statusCode := fPluginVersionW(@buf, cchBufferSize, lngID);
  955.              if statusCode.wCategory = UD2_STATUSCAT_SUCCESS   then Plugin.FPluginVersion := PWideChar(@buf)
  956.         else if statusCode.wCategory = UD2_STATUSCAT_NOT_AVAIL then Plugin.FPluginVersion := ''
  957.         else
  958.         begin
  959.           Errors.Add(Format(LNG_METHOD_FAILURE, [_ErrorLookup(statusCode), mnPluginVersionW, dllFile]));
  960.           Exit;
  961.         end;
  962.  
  963.         ZeroMemory(@buf, cchBufferSize*SizeOf(WideChar));
  964.         statusCode := fIdentificationMethodNameW(@buf, cchBufferSize);
  965.              if statusCode.wCategory = UD2_STATUSCAT_SUCCESS   then Plugin.FIdentificationMethodName := PWideChar(@buf)
  966.         else if statusCode.wCategory = UD2_STATUSCAT_NOT_AVAIL then Plugin.FIdentificationMethodName := ''
  967.         else
  968.         begin
  969.           Errors.Add(Format(LNG_METHOD_FAILURE, [_ErrorLookup(statusCode), mnIdentificationMethodNameW, dllFile]));
  970.           Exit;
  971.         end;
  972.  
  973.         ZeroMemory(@buf, cchBufferSize*SizeOf(WideChar));
  974.         statusCode := UD2_STATUS_FAILURE_NO_RETURNED_VALUE; // This status will be used when the DLL does not return anything (which is an error by the developer)
  975.         if useDynamicData then
  976.         begin
  977.           statusCode := fDynamicIdentificationStringW(@buf, cchBufferSize, PWideChar(dynamicData));
  978.         end
  979.         else
  980.         begin
  981.           statusCode := fIdentificationStringW(@buf, cchBufferSize);
  982.         end;
  983.         Plugin.FIdentificationProcedureStatusCode := statusCode;
  984.         Plugin.FIdentificationProcedureStatusCodeDescribed := _ErrorLookup(statusCode);
  985.         if statusCode.wCategory = UD2_STATUSCAT_SUCCESS then
  986.         begin
  987.           sIdentifier := PWideChar(@buf);
  988.           if UD2_STATUS_Equal(statusCode, UD2_STATUS_OK_MULTILINE, false) then
  989.           begin
  990.             // Multiple identifiers (e.g. multiple MAC addresses are delimited via UD2_MULTIPLE_ITEMS_DELIMITER)
  991.             SetLength(ResultIdentifiers, 0);
  992.             ResultIdentifiers := SplitString(UD2_MULTIPLE_ITEMS_DELIMITER, sIdentifier);
  993.             for i := Low(ResultIdentifiers) to High(ResultIdentifiers) do
  994.             begin
  995.               Plugin.AddIdentification(ResultIdentifiers[i]);
  996.             end;
  997.           end
  998.           else
  999.           begin
  1000.             Plugin.AddIdentification(sIdentifier);
  1001.  
  1002.             SetLength(ResultIdentifiers, 1);
  1003.             ResultIdentifiers[0] := sIdentifier;
  1004.           end;
  1005.         end
  1006.         else if statusCode.wCategory <> UD2_STATUSCAT_NOT_AVAIL then
  1007.         begin
  1008.           if _AutoOSNotSupportedMode >= 3 then
  1009.           begin
  1010.             _OverwriteStatusToOSNotSupported;
  1011.             Exit;
  1012.           end;
  1013.  
  1014.           // Errors.Add(Format(LNG_METHOD_FAILURE, [_ErrorLookup(statusCode), mnIdentificationStringW, dllFile]));
  1015.           Errors.Add(Format(LNG_METHOD_FAILURE, [Plugin.IdentificationProcedureStatusCodeDescribed, mnIdentificationStringW, dllFile]));
  1016.           Exit;
  1017.         end;
  1018.  
  1019.         result := true;
  1020.       finally
  1021.         if not result and Assigned(Plugin) then FreeAndNil(Plugin);
  1022.         FreeLibrary(dllHandle);
  1023.       end;
  1024.     finally
  1025.       UD2_SetThreadErrorMode(bakErrorMode, nil);
  1026.  
  1027.       if result then
  1028.       begin
  1029.         endtime := GetTickCount;
  1030.         time := endtime - starttime;
  1031.         if endtime < starttime then time := High(Cardinal) - time;
  1032.         Plugin.FLoadingTime := time;
  1033.       end;
  1034.     end;
  1035.   except
  1036.     // TODO: when an exception happens in a cdecl DLL, then this code is somehow not
  1037.     // executed. Probably the memory is corrupted. Anyway, a cdecl DLL shall NEVER
  1038.     // raise an Exception.
  1039.     on E: Exception do
  1040.     begin
  1041.       Errors.Add(Format(LNG_EXCEPTION, [dllFile, E.ClassName, E.Message]));
  1042.       Exit;
  1043.     end;
  1044.   end;
  1045. end;
  1046.  
  1047. end.
  1048.