{*******************************************************} { } { ParserLinkFile } { } { Copyright (C) 2022 kku } { } {*******************************************************} unit ParserLinkFile; interface uses System.SysUtils, System.Classes, Winapi.Windows, System.Generics.Collections; const HasLinkTargetIDList = $1; // The shell link is saved with an item ID list (IDList). HasLinkInfo = $2; // The shell link is saved with link information or without information. The header is available. HasName = $4; // The shell link is saved with a name string. HasRelativePath = $8; // The shell link is saved with a relative path string. HasWorkingDir = $10; // The shell link is saved with a working directory string. HasArguments = $20; // The shell link is saved with command line arguments. HasIconLocation = $40; // The shell link is saved with an icon location string. IsUnicode = $80; // The shell link contains Unicode encoded strings. This bit SHOULD be set. ForceNoLinkInfo = $100; // The LinkInfo structure is ignored. HasExpString = $200; // The shell link is saved with an EnvironmentVariableDataBlock RunInSeparateProcess = $400; // The target is run in a separate virtual machine when launching a link target that is a 16-bit application. Unused1 = $800; // A bit that is undefined and MUST be ignored. HasDarwinID = $1000; // The shell link is saved with a DarwinDataBlock RunAsUser = $2000; // The application is run as a different user when the target of the shell link is activated. HasExpIcon = $4000; // The shell link is saved with an IconEnvironmentDataBlock NoPidlAlias = $8000; // The file system location is represented in the shell namespace when the path to an item is parsed into an IDList. Unused2 = $10000; // A bit that is undefined and MUST be ignored. RunWithShimLayer = $20000; // The shell link is saved with a ShimDataBlock ForceNoLinkTrack = $40000; // The TrackerDataBlock is ignored EnableTargetMetadata = $80000; // The shell link attempts to collect target properties and store them in // the PropertyStoreDataBlock (section 2.5.7) when the link target is set. DisableLinkPathTracking = $100000; // The EnvironmentVariableDataBlock is ignored. DisableKnownFolderTracking = $200000; // The SpecialFolderDataBlock (section 2.5.9) and the KnownFolderDataBlock are ignored when loading the shell link. DisableKnownFolderAlias = $400000; // If the link has a KnownFolderDataBlock, the unaliased form of the known folder IDList SHOULD be used when // translating the target IDList at the time that the link is loaded. AllowLinkToLink = $800000; // Creating a link that references another link is enabled. Otherwise, specifying a link as the target IDList SHOULD NOT be allowed UnaliasOnSave = $1000000; // When saving a link for which the target IDList is under a known folder, either the unaliased form of that known folder or the target IDList SHOULD be used. PreferEnvironmentPath = $2000000; // The target IDList SHOULD NOT be stored; instead, the path specified in the EnvironmentVariableDataBlock SHOULD be used to refer to the target. KeepLocalIDListForUNCTarget = $4000000; // When the target is a UNC name that refers to a location on a local machine, the local path IDList in the PropertyStoreDataBlock // SHOULD be stored, so it can be used when the link is loaded on the local machine. Unused3 = $8000000; // A bit that is undefined and MUST be ignored. Unused4 = $10000000; // A bit that is undefined and MUST be ignored. Unused5 = $20000000; // A bit that is undefined and MUST be ignored. Unused6 = $40000000; // A bit that is undefined and MUST be ignored. Unused7 = $80000000; // A bit that is undefined and MUST be ignored. VolumeIDAndLocalBasePath = $1; CommonNetworkRelativeLinkAndPathSuffix = $2; ValidDevice = $1; ValidNetType = $2; type TLinkFileHeader = packed record dwHeaderSize: DWORD; // 헤더의 크기로 항상 0x0000004C(76) 값 // arrLinkCLSID: array [0..15] of AnsiChar; // 클래스 식별자(class identifier;CLSID)로 항상 00021401-0000-0000-C000-000000000046 값 CLSID: TGUID; // 클래스 식별자(class identifier;CLSID)로 항상 00021401-0000-0000-C000-000000000046 값 dwLinkFlags, // 링크 대상의 다양한 정보에 대한 플래그 dwFileAttrFlags: DWORD; // 링크 대상의 파일 특성 정보 ftCreate, // 링크 대상의 생성 시간 ftAccess, // 링크 대상의 접근 시간 ftModify: TFileTime; // 링크 대상의 쓰기 시간 dwFileSize, // 링크 대상의 크기 dwIconIndex, // 아이콘 인덱스 dwShowCommand: DWORD; // 링크가 실행될 때 응용프로그램의 동작 모드 wHotKey, // 핫키 정보 wPatch: WORD; // 예약된 필드 (항상 0), Has a 0x00 entry dwReserved1, // 예약된 필드 (항상 0) dwReserved2: DWORD; // 예약된 필드 (항상 0) end; TLinkFileLocationInfo = packed record dwLinkInfoSize, dwLinkInfoHeaderSize, dwLinkInfoFlags, dwOffsetVolumeTable, dwOffsetBasePath, dwOffsetNetwork, dwOffsetFinalPath: DWORD; end; TLinkFileLocalVolumeTable = packed record dwVolumeTableLength, dwVolumeType, dwVolumeSerialNbr, dwOffsetVolumeName: DWORD; end; TLinkFileNetworkVolumeTable = packed record dwNetVolumeLength, dwNetFlags, dwNetNameOffset, dwDeviceNameOffset, dwNetProviderType: DWORD; end; PLfiEnt = ^TLfiEnt; TLfiEntList = TList; TLfiEnt = record sCaption, sValue: String; ChildList: TLfiEntList; end; TParserLinkFile = class(TObject) private LfiEntList_: TLfiEntList; procedure OnEntNotify(Sender: TObject; const Item: PLfiEnt; Action: TCollectionNotification); function AddLinkFileInfo(pParent: PLfiEnt; sCaption, sValue: String; nInsertIdx: Integer = -1): PLfiEnt; public Constructor Create; Destructor Destroy; override; function LoadFromStream(aStream: TStream; bClear: Boolean = true; nIncUtcMin: Integer = -1): Boolean; function LoadFromFile(sPath: String): Boolean; property LfiEntList: TLfiEntList read LfiEntList_; end; function GetLfiValueFromCaption(aList: TLfiEntList; sCaption: String): String; implementation uses System.DateUtils, Tocsg.Convert, Tocsg.DateTime, Tocsg.Safe, Tocsg.Keyboard, Tocsg.Files; resourcestring RS_LinkFIleInfo = 'Link 파일 정보'; RS_Name = '이름'; RS_Path = '경로'; RS_CreateDT = '생성일'; RS_AccessDT = '사용일'; RS_ModifyDT = '수정일'; RS_LinkFIleHeaderInfo = 'Link 파일 헤더 정보'; RS_HeaderSize = '헤더 크기'; RS_ExistsIDListInShellHeader = 'ShellLinkHeader에 ID List가 있습니다.'; RS_NoExistsIDListInShellHeader = 'ShellLinkHeader에 ID List가 존재하지 않습니다.'; RS_LinkInfoPresent = 'Link Info가 존재합니다.'; RS_NoLinkInfoAvailable = 'Link Info가 존재하지 않습니다.'; RS_ShellLinkNamePresent = 'Shell link 이름이 존재합니다.'; RS_RelativeShellLinkPathUsed = 'Shell link 경로가 사용됩니다.'; RS_ShellWorkingDirEntered = 'Shell Working Directory가 입력되었습니다.'; RS_CommandLineArgumentsEntered = 'Command line 인수가 입력되었습니다.'; RS_IconLocationEntered = 'Icon 위치가 입력되었습니다.'; RS_LinkContainsUnicode = 'Link에 유니코드가 포함되어 있습니다.'; RS_LinkInfoStructureIgnored = 'Link 정보 구조가 무시됩니다.'; RS_LinkSavedInEnvironmentVariableDataBlock = 'Link는 EnvironmentVariableDataBlock에 저장됩니다.'; RS_RunTargetInVirtualMachine = '가상 컴퓨터에서 대상 실행 (16 비트 응용 프로그램)'; RS_LinkHasDrawinSection = 'Link에 Darwin section이 있습니다.'; RS_LinkRunsAsDifferentUser = 'Link가 다른 사용자로 실행됩니다.'; RS_LinkSavedWithIconEnvironmentDataBlock = 'Link는 IconEnvironmentDataBlock과 함께 저장됩니다.'; RS_FileSystemLocationRepresentedInShellNamespace = '파일 시스템 위치가 shell namespace에 표시됩니다.'; RS_ShellLinkHasShimDataBlock = 'Shell link에 ShimDataBlock이 있습니다.'; RS_TrackerDataBlockIgnored = 'TrackerDataBlock이 무시됩니다.'; RS_ShellLinkCollectsTargetProperties = 'Shell link가 대상 속성을 수집합니다.'; RS_EnvironmentVariableDataBlockIgnored = 'EnvironmentVariableDataBlock이 무시됩니다.'; RS_SpecialFolderDataBlockNKnownFolderDataBlockIgnored = 'SpecialFolderDataBlock 및 KnownFolderDataBlock이 무시됩니다.'; RS_KnownFolderDataBlock4TranslatingTargetIDList = '대상 ID List 변환에 KnownFolderDataBlock를 사용합니다.'; RS_CreatingLinkReferncesAnotherLink = '다른 Link를 참조하는 Link를 만들 수 있습니다.'; RS_UseUnaliased = 'Link를 저장하기 위해 Alias화 되지 않은 형식의 알려진 폴더 또는 대상 ID List를 사용하십시오.'; RS_TargetIDListShouldNotStored = '대상 ID List를 저장해서는 안됩니다 (SHOULD NOT SHOULD NOT).'; RS_StorePropertyStoreDataBlockIfTargetUNCName = '대상이 UNC 이름 인 경우 PropertyStoreDataBlock을 저장하십시오.'; RS_TargetFileProp = '대상 파일 속성'; RS_TargetFileCreateDT = '대상 파일 생성일'; RS_TargetFileAccessDT = '대상 파일 사용일'; RS_TargetFileModifyDT = '대상 파일 수정일'; RS_TargetFileSize = '대상 파일 크기'; RS_IconIndex = '아이콘 인덱스'; RS_HotKey = '바로 가기 키'; RS_ReservedField1 = '예약 필드1'; RS_ReservedField2 = '예약 필드2'; RS_ReservedField3 = '예약 필드3'; RS_TargetFilePath = '대상 파일 위치'; RS_Network = '네트워크'; RS_Local = '로컬'; RS_TargetFilePathInfo = '대상 파일 경로 정보'; RS_VolumeName = '볼륨 이름'; RS_VolumeType = '볼륨 종류'; function GetLfiValueFromCaption(aList: TLfiEntList; sCaption: String): String; var i: Integer; begin Result := ''; if aList = nil then exit; for i := 0 to aList.Count - 1 do begin if CompareText(aList[i].sCaption, sCaption) = 0 then begin Result := aList[i].sValue; exit; end; Result := GetLfiValueFromCaption(aList[i].ChildList, sCaption); if Result <> '' then exit; end; end; { TParserLinkFile } Constructor TParserLinkFile.Create; begin Inherited Create; LfiEntList_ := TLfiEntList.Create; LfiEntList_.OnNotify := OnEntNotify; end; Destructor TParserLinkFile.Destroy; begin FreeAndNil(LfiEntList_); Inherited; end; procedure TParserLinkFile.OnEntNotify(Sender: TObject; const Item: PLfiEnt; Action: TCollectionNotification); begin case Action of cnAdded: ; cnRemoved: begin if Item.ChildList <> nil then FreeAndNil(Item.ChildList); Dispose(Item); end; cnExtracted: ; end; end; function TParserLinkFile.AddLinkFileInfo(pParent: PLfiEnt; sCaption, sValue: String; nInsertIdx: Integer = -1): PLfiEnt; begin New(Result); ZeroMemory(Result, SizeOf(TLfiEnt)); Result.sCaption := sCaption; Result.sValue := sValue; if pParent <> nil then begin if pParent.ChildList = nil then begin pParent.ChildList := TLfiEntList.Create; pParent.ChildList.OnNotify := OnEntNotify; end; if (nInsertIdx >= 0) and (nInsertIdx < pParent.ChildList.Count) then pParent.ChildList.Insert(nInsertIdx, Result) else pParent.ChildList.Add(Result); end else begin if (nInsertIdx >= 0) and (nInsertIdx < LfiEntList_.Count) then LfiEntList_.Insert(nInsertIdx, Result) else LfiEntList_.Add(Result); end; end; function TParserLinkFile.LoadFromStream(aStream: TStream; bClear: Boolean = true; nIncUtcMin: Integer = -1): Boolean; function GetAnsiString: AnsiString; var b: Byte; begin Result := ''; while aStream.Size > aStream.Position do begin if aStream.Read(b, 1) <> 1 then exit; if b = 0 then exit; Result := Result + AnsiChar(b); end; end; function GetUnicodeString: String; var wLen, w: WORD; begin Result := ''; if aStream.Read(wLen, 2) <> 2 then exit; while aStream.Size > aStream.Position do begin if wLen = 0 then exit; if aStream.Read(w, 2) <> 2 then exit; if w = 0 then exit; Result := Result + Char(w); Dec(wLen); end; end; var LFH: TLinkFileHeader; pParent, pEnt, pSubEnt: PLfiEnt; wListLen, wTargetIdListSize: WORD; i: Integer; // dwPos: DWORD; LFLI: TLinkFileLocationInfo; LFLVT: TLinkFileLocalVolumeTable; LFNVT: TLinkFileNetworkVolumeTable; sTemp: String; begin Result := true; try if bClear then LfiEntList_.Clear; ZeroMemory(@LFH, SizeOf(LFH)); aStream.Read(LFH, SizeOf(LFH)); if AnsiString(@LFH)[1] = '[' then exit; if nIncUtcMin = -1 then nIncUtcMin := (TTimeZone.Local.UtcOffset.Hours * 60) + TTimeZone.Local.UtcOffset.Minutes; pParent := AddLinkFileInfo(nil, RS_LinkFIleHeaderInfo, ''); AddLinkFileInfo(pParent, RS_HeaderSize, Format('%s (%d)', [ByteSizeToStr(LFH.dwHeaderSize), LFH.dwHeaderSize])); AddLinkFileInfo(pParent, 'CLSID', GUIDToString(LFH.CLSID)); pEnt := AddLinkFileInfo(pParent, 'Link Flags', IntToStr(LFH.dwLinkFlags)); if (LFH.dwLinkFlags and HasLinkTargetIDList) <> 0 then AddLinkFileInfo(pEnt, 'HasLinkTargetIDList', RS_ExistsIDListInShellHeader) else AddLinkFileInfo(pEnt, 'HasLinkTargetIDList', RS_NoExistsIDListInShellHeader); if (LFH.dwLinkFlags and HasLinkInfo) <> 0 then AddLinkFileInfo(pEnt, 'HasLinkInfo', RS_LinkInfoPresent) else AddLinkFileInfo(pEnt, 'HasLinkInfo', RS_NoLinkInfoAvailable); if (LFH.dwLinkFlags and HasName) <> 0 then AddLinkFileInfo(pEnt, 'HasName', RS_ShellLinkNamePresent); if (LFH.dwLinkFlags and HasRelativePath) <> 0 then AddLinkFileInfo(pEnt, 'HasRelativePath', RS_RelativeShellLinkPathUsed); if (LFH.dwLinkFlags and HasWorkingDir) <> 0 then AddLinkFileInfo(pEnt, 'HasWorkingDir', RS_ShellWorkingDirEntered); if (LFH.dwLinkFlags and HasArguments) <> 0 then AddLinkFileInfo(pEnt, 'HasArguments', RS_CommandLineArgumentsEntered); if (LFH.dwLinkFlags and HasIconLocation) <> 0 then AddLinkFileInfo(pEnt, 'HasIconLocation', RS_IconLocationEntered); if (LFH.dwLinkFlags and IsUnicode) <> 0 then AddLinkFileInfo(pEnt, 'IsUnicode', RS_LinkContainsUnicode); if (LFH.dwLinkFlags and ForceNoLinkInfo) <> 0 then AddLinkFileInfo(pEnt, 'ForceNoLinkInfo', RS_LinkInfoStructureIgnored); if (LFH.dwLinkFlags and HasExpString) <> 0 then AddLinkFileInfo(pEnt, 'HasExpString', RS_LinkSavedInEnvironmentVariableDataBlock); if (LFH.dwLinkFlags and RunInSeparateProcess) <> 0 then AddLinkFileInfo(pEnt, 'RunInSeparateProcess', RS_RunTargetInVirtualMachine); if (LFH.dwLinkFlags and Unused1) <> 0 then AddLinkFileInfo(pEnt, 'Unused1', 'unused? Unknown! Bit 0x800'); if (LFH.dwLinkFlags and HasDarwinID) <> 0 then AddLinkFileInfo(pEnt, 'HasDarwinID', RS_LinkHasDrawinSection); if (LFH.dwLinkFlags and RunAsUser) <> 0 then AddLinkFileInfo(pEnt, 'RunAsUser', RS_LinkRunsAsDifferentUser); if (LFH.dwLinkFlags and HasExpIcon) <> 0 then AddLinkFileInfo(pEnt, 'HasExpIcon', RS_LinkSavedWithIconEnvironmentDataBlock); if (LFH.dwLinkFlags and NoPidlAlias) <> 0 then AddLinkFileInfo(pEnt, 'NoPidlAlias', RS_FileSystemLocationRepresentedInShellNamespace); if (LFH.dwLinkFlags and Unused2) <> 0 then AddLinkFileInfo(pEnt, 'Unused2', 'unused? Unknown! Bit 0x10000'); if (LFH.dwLinkFlags and RunWithShimLayer) <> 0 then AddLinkFileInfo(pEnt, 'RunWithShimLayer', RS_ShellLinkHasShimDataBlock); if (LFH.dwLinkFlags and ForceNoLinkTrack) <> 0 then AddLinkFileInfo(pEnt, 'ForceNoLinkTrack', RS_TrackerDataBlockIgnored); if (LFH.dwLinkFlags and EnableTargetMetadata) <> 0 then AddLinkFileInfo(pEnt, 'EnableTargetMetadata', RS_ShellLinkCollectsTargetProperties); if (LFH.dwLinkFlags and DisableLinkPathTracking) <> 0 then AddLinkFileInfo(pEnt, 'DisableLinkPathTracking', RS_EnvironmentVariableDataBlockIgnored); if (LFH.dwLinkFlags and DisableKnownFolderTracking) <> 0 then AddLinkFileInfo(pEnt, 'DisableKnownFolderTracking', RS_SpecialFolderDataBlockNKnownFolderDataBlockIgnored); if (LFH.dwLinkFlags and DisableKnownFolderAlias) <> 0 then AddLinkFileInfo(pEnt, 'DisableKnownFolderAlias', RS_KnownFolderDataBlock4TranslatingTargetIDList); if (LFH.dwLinkFlags and AllowLinkToLink) <> 0 then AddLinkFileInfo(pEnt, 'AllowLinkToLink', RS_CreatingLinkReferncesAnotherLink); if (LFH.dwLinkFlags and UnaliasOnSave) <> 0 then AddLinkFileInfo(pEnt, 'UnaliasOnSave', RS_UseUnaliased); if (LFH.dwLinkFlags and PreferEnvironmentPath) <> 0 then AddLinkFileInfo(pEnt, 'PreferEnvironmentPath', RS_TargetIDListShouldNotStored); if (LFH.dwLinkFlags and KeepLocalIDListForUNCTarget) <> 0 then AddLinkFileInfo(pEnt, 'KeepLocalIDListForUNCTarget', RS_StorePropertyStoreDataBlockIfTargetUNCName); if (LFH.dwLinkFlags and Unused3) <> 0 then AddLinkFileInfo(pEnt, 'Unused3', 'unused? Unknown! Bit 0x8000000'); if (LFH.dwLinkFlags and Unused4) <> 0 then AddLinkFileInfo(pEnt, 'Unused4', 'unused? Unknown! Bit 0x10000000'); if (LFH.dwLinkFlags and Unused5) <> 0 then AddLinkFileInfo(pEnt, 'Unused5', 'unused? Unknown! Bit 0x20000000'); if (LFH.dwLinkFlags and Unused6) <> 0 then AddLinkFileInfo(pEnt, 'Unused6', 'unused? Unknown! Bit 0x40000000'); if (LFH.dwLinkFlags and Unused7) <> 0 then AddLinkFileInfo(pEnt, 'Unused7', 'unused? Unknown! Bit 0x80000000'); if LFH.dwFileAttrFlags <> 0 then AddLinkFileInfo(pParent, RS_TargetFileProp, Format('%s (%d)', [ConvFileAttrToStr(LFH.dwFileAttrFlags), LFH.dwFileAttrFlags])); if (LFH.ftCreate.dwLowDateTime <> 0) and (LFH.ftCreate.dwHighDateTime <> 0) then AddLinkFileInfo(pParent, RS_TargetFileCreateDT, DateTimeToStr(IncMinute(ConvFileTimeToDateTime(LFH.ftCreate), nIncUtcMin))); if (LFH.ftAccess.dwLowDateTime <> 0) and (LFH.ftAccess.dwHighDateTime <> 0) then AddLinkFileInfo(pParent, RS_TargetFileAccessDT, DateTimeToStr(IncMinute(ConvFileTimeToDateTime(LFH.ftAccess), nIncUtcMin))); if (LFH.ftModify.dwLowDateTime <> 0) and (LFH.ftModify.dwHighDateTime <> 0) then AddLinkFileInfo(pParent, RS_TargetFileModifyDT, DateTimeToStr(IncMinute(ConvFileTimeToDateTime(LFH.ftModify), nIncUtcMin))); if LFH.dwFileSize <> 0 then AddLinkFileInfo(pParent, RS_TargetFileSize, Format('%s (%d)', [ByteSizeToStr(LFH.dwFileSize), LFH.dwFileSize])); AddLinkFileInfo(pParent, RS_IconIndex, IntToStr(LFH.dwIconIndex)); AddLinkFileInfo(pParent, RS_HotKey, ConvHotkeyToStr(LFH.wHotKey)); AddLinkFileInfo(pParent, RS_ReservedField1, IntToStr(LFH.wPatch)); AddLinkFileInfo(pParent, RS_ReservedField2, IntToStr(LFH.dwReserved1)); AddLinkFileInfo(pParent, RS_ReservedField3, IntToStr(LFH.dwReserved2)); if (LFH.dwLinkFlags and HasLinkTargetIDList) <> 0 then begin wTargetIdListSize := 0; aStream.Read(wTargetIdListSize, 2); pEnt := AddLinkFileInfo(pParent, 'Target ID List', Format('0x%.4X (Size=%d)', [aStream.Position - 2, wTargetIdListSize])); if wTargetIdListSize > 0 then begin i := 1; while aStream.Size > aStream.Position do begin if aStream.Read(wListLen, 2) <> 2 then begin AddLinkFileInfo(pEnt, 'EOF', '....'); exit; end; if wListLen <= 2 then begin AddLinkFileInfo(pEnt, 'Terminator found', ''); break; end; AddLinkFileInfo(pEnt, Format('ID List entry - %.2d', [i]), Format('0x%.4X (Size=%d)', [aStream.Position - 2, wListLen])); aStream.Position := aStream.Position + (wListLen - 2); Inc(i); end; end; end; pEnt := nil; if (LFH.dwLinkFlags and HasLinkInfo) <> 0 then begin pEnt := AddLinkFileInfo(pParent, 'Link Info List', Format('0x%.4X', [aStream.Position])); // dwPos := aStream.Position; ZeroMemory(@LFLI, SizeOf(LFLI)); if aStream.Read(LFLI, SizeOf(LFLI)) <> SizeOf(LFLI) then begin AddLinkFileInfo(pEnt, 'EOF', '....'); exit; end; AddLinkFileInfo(pEnt, 'Table Length', Format('%s (%d)', [ByteSizeToStr(LFLI.dwLinkInfoSize), LFLI.dwLinkInfoSize])); AddLinkFileInfo(pEnt, 'Header Length', Format('%s (%d)', [ByteSizeToStr(LFLI.dwLinkInfoHeaderSize), LFLI.dwLinkInfoHeaderSize])); if LFLI.dwLinkInfoFlags <> 0 then begin pSubEnt := AddLinkFileInfo(pEnt, 'Link Info Flags', ''); if (LFLI.dwLinkInfoFlags and VolumeIDAndLocalBasePath) <> 0 then AddLinkFileInfo(pSubEnt, 'Flags 0x1', 'VolumeIDAndLocalBasePath'); if (LFLI.dwLinkInfoFlags and CommonNetworkRelativeLinkAndPathSuffix) <> 0 then begin AddLinkFileInfo(pSubEnt, 'Flags 0x2', 'CommonNetworkRelativeLinkAndPathSuffix'); AddLinkFileInfo(pSubEnt, RS_TargetFilePath, RS_Network); end else AddLinkFileInfo(pSubEnt, RS_TargetFilePath, RS_Local); end; pSubEnt := nil; if (LFLI.dwLinkInfoFlags and VolumeIDAndLocalBasePath) <> 0 then begin pSubEnt := AddLinkFileInfo(nil, RS_TargetFilePathInfo, '', 0); // 이 부분은 굳이 구현하지 않아도 될거 같아서 패스 if LFLI.dwLinkInfoHeaderSize > $1C then begin (* buff = reader.ReadBytes(4); LocalBasePathOffsetUnicode = ptrToInt32(buff); basePath = (UInt32)fs.Position + LocalBasePathOffsetUnicode - 4; dataGridView1.Rows.Add("Base Path", "@ 0x" + makeLen8String(basePath, 4)); buff = reader.ReadBytes(4); CommonPathSuffixOffsetUnicode = ptrToInt32(buff); if (LinkFileLocations.LinkInfoHeaderSize >= 0x24) { suffix = basePath + CommonPathSuffixOffsetUnicode; dataGridView1.Rows.Add("Suffix", "@ 0x" + makeLen8String(suffix, 4)); } *) aStream.Position := aStream.Position + 4; // buff = reader.ReadBytes(4); aStream.Position := aStream.Position + 4; // buff = reader.ReadBytes(4); end; ZeroMemory(@LFLVT, SizeOf(LFLVT)); if aStream.Read(LFLVT, SizeOf(LFLVT)) <> SizeOf(LFLVT) then begin AddLinkFileInfo(pSubEnt, 'EOF', '....'); exit; end; AddLinkFileInfo(pSubEnt, RS_VolumeName, GetAnsiString); case LFLVT.dwVolumeType of 1 : sTemp := 'No root directory'; 2 : sTemp := 'Removable (Floppy, Zip, etc..)'; 3 : sTemp := 'Fixed (Hard disk)'; 4 : sTemp := 'Remote (Network drive)'; 5 : sTemp := 'CD-ROM'; 6 : sTemp := 'Ram drive'; else sTemp := 'Unknown'; end; AddLinkFileInfo(pSubEnt, RS_VolumeType, sTemp); // ........ (* dataGridView2.Rows.Add(" Volume Entries:"); dataGridView2.Rows.Add(" Volume Length 0x" + makeLen8String((UInt32)LocalVolumeTable.volumeTableLength, 4) + " Bytes"); dataGridView2.Rows.Add(" Volume Type = 0x" + makeLen8String((UInt32)LocalVolumeTable.volumeType, 4) + " -" + shlp); dataGridView2.Rows.Add(" Volume Serial # 0x" + makeLen8String((UInt32)LocalVolumeTable.volumeSerialNbr, 0) + " (" + (long)LocalVolumeTable.volumeSerialNbr + ")"); dataGridView2.Rows.Add(" Volume Name @ 0x" + makeLen8String((UInt32)LocalVolumeTable.offsetVolumeName + loc, 4)); *) end; if ((LFLI.dwLinkInfoFlags and VolumeIDAndLocalBasePath) = 0) and ((LFLI.dwLinkInfoFlags and CommonNetworkRelativeLinkAndPathSuffix) <> 0) then begin if pSubEnt = nil then pSubEnt := AddLinkFileInfo(nil, RS_TargetFilePathInfo, '', 0); //가장 위에 보이도록 수정 ZeroMemory(@LFNVT, SizeOf(LFNVT)); if aStream.Read(LFNVT, SizeOf(LFNVT)) <> SizeOf(LFNVT) then begin AddLinkFileInfo(pSubEnt, 'EOF', '....'); exit; end; if LFNVT.dwNetNameOffset > $14 then begin { buff = reader.ReadBytes(4); NetNameOffsetUnicode = ptrToInt32(buff); buff = reader.ReadBytes(4); DeviceNameOffsetUnicode = ptrToInt32(buff); } aStream.Position := aStream.Position + 4; // buff = reader.ReadBytes(4); aStream.Position := aStream.Position + 4; // buff = reader.ReadBytes(4); end; if (LFNVT.dwNetFlags and ValidNetType) <> 0 then begin case LFNVT.dwNetProviderType of $00020000 : sTemp := 'WNNC_NET_SMB'; $00440000 : sTemp := 'WNNC_NET_NDFS'; $001A0000 : sTemp := 'WNNC_NET_AVID'; $001B0000 : sTemp := 'WNNC_NET_DOCUSPACE'; $001C0000 : sTemp := 'WNNC_NET_MANGOSOFT'; $001D0000 : sTemp := 'WNNC_NET_SERNET'; $001E0000 : sTemp := 'WNNC_NET_RIVERFRONT1'; $001F0000 : sTemp := 'WNNC_NET_RIVERFRONT2'; $00200000 : sTemp := 'WNNC_NET_DECORB'; $00210000 : sTemp := 'WNNC_NET_PROTSTOR'; $00220000 : sTemp := 'WNNC_NET_FJ_REDIR'; $00230000 : sTemp := 'WNNC_NET_DISTINCT'; $00240000 : sTemp := 'WNNC_NET_TWINS'; $00250000 : sTemp := 'WNNC_NET_RDR2SAMPLE'; $00260000 : sTemp := 'WNNC_NET_CSC'; $00270000 : sTemp := 'WNNC_NET_3IN1'; $00290000 : sTemp := 'WNNC_NET_EXTENDNET'; $002A0000 : sTemp := 'WNNC_NET_STAC'; $002B0000 : sTemp := 'WNNC_NET_FOXBAT'; $002C0000 : sTemp := 'WNNC_NET_YAHOO'; $002D0000 : sTemp := 'WNNC_NET_EXIFS'; $002E0000 : sTemp := 'WNNC_NET_DAV'; $002F0000 : sTemp := 'WNNC_NET_KNOWARE'; $00300000 : sTemp := 'WNNC_NET_OBJECT_DIRE'; $00310000 : sTemp := 'WNNC_NET_MASFAX'; $00320000 : sTemp := 'WNNC_NET_HOB_NFS'; $00330000 : sTemp := 'WNNC_NET_SHIVA'; $00340000 : sTemp := 'WNNC_NET_IBMAL'; $00350000 : sTemp := 'WNNC_NET_LOCK'; $00360000 : sTemp := 'WNNC_NET_TERMSRV'; $00370000 : sTemp := 'WNNC_NET_SRT'; $00380000 : sTemp := 'WNNC_NET_QUINCY'; $00390000 : sTemp := 'WNNC_NET_OPENAFS'; $003A0000 : sTemp := 'WNNC_NET_AVID1'; $003B0000 : sTemp := 'WNNC_NET_DFS'; $003C0000 : sTemp := 'WNNC_NET_KWNP'; $003D0000 : sTemp := 'WNNC_NET_ZENWORKS'; $003E0000 : sTemp := 'WNNC_NET_DRIVEONWEB'; $003F0000 : sTemp := 'WNNC_NET_VMWARE'; $00400000 : sTemp := 'WNNC_NET_RSFX'; $00410000 : sTemp := 'WNNC_NET_MFILES'; $00420000 : sTemp := 'WNNC_NET_MS_NFS'; $00430000 : sTemp := 'WNNC_NET_GOOGLE'; else sTemp := 'Unknown - ' + Format('%.8X (%d)', [LFNVT.dwNetProviderType, LFNVT.dwNetProviderType]); end; AddLinkFileInfo(pSubEnt, 'Network Provider Type', sTemp); end; AddLinkFileInfo(pSubEnt, 'Network Relative Link', GetAnsiString); sTemp := GetAnsiString; if sTemp <> '' then begin AddLinkFileInfo(pSubEnt, 'File Name', ExtractFileName(sTemp)); AddLinkFileInfo(pSubEnt, 'Net Name', sTemp); end; exit; end; sTemp := GetAnsiString; if sTemp <> '' then begin if CompareText('c:\users\', sTemp) = 0 then begin // 윈도우 11 최근 업데이트 부터... 바탕화면에 있는 파일이 최근 문서(LNK)로 등록될때 // 기존처럼 가져와 지지 않는다... 아래처럼 추가 처리 필요... 23_0823 18:47:19 kku aStream.Position := aStream.Position + 21; GetAnsiString; // \\KKU-PC\Users << 이런 형태 sTemp := sTemp + GetAnsiString; end; AddLinkFileInfo(pSubEnt, 'File Name', ExtractFileName(sTemp)); AddLinkFileInfo(pSubEnt, 'Base Path', sTemp); end; sTemp := GetAnsiString; if sTemp <> '' then AddLinkFileInfo(pSubEnt, 'Common Base Path', GetUnicodeString); if (LFH.dwLinkFlags and HasName) <> 0 then AddLinkFileInfo(pSubEnt, 'Name', GetUnicodeString); if (LFH.dwLinkFlags and HasRelativePath) <> 0 then AddLinkFileInfo(pSubEnt, 'Relative Path', GetUnicodeString); if (LFH.dwLinkFlags and HasWorkingDir) <> 0 then AddLinkFileInfo(pSubEnt, 'Working Directory', GetUnicodeString); if (LFH.dwLinkFlags and HasArguments) <> 0 then AddLinkFileInfo(pSubEnt, 'Arguments', GetUnicodeString); if (LFH.dwLinkFlags and HasIconLocation) <> 0 then AddLinkFileInfo(pSubEnt, 'Icon Location', GetUnicodeString); end; except Result := false; end; end; function TParserLinkFile.LoadFromFile(sPath: String): Boolean; var fs: TFileStream; pParent: PLfiEnt; dtCreate, dtAccess, dtModify: TDateTime; begin Result := false; try LfiEntList_.Clear; if not FileExists(sPath) then exit; pParent := AddLinkFileInfo(nil, RS_LinkFIleInfo, ''); AddLinkFileInfo(pParent, RS_Name, ExtractFileName(sPath)); AddLinkFileInfo(pParent, RS_Path, ExtractFilePath(sPath)); if GetFileDateTime_Local(sPath, dtCreate, dtModify, dtAccess) then begin AddLinkFileInfo(pParent, RS_CreateDT, DateTimeToStr(dtCreate)); AddLinkFileInfo(pParent, RS_AccessDT, DateTimeToStr(dtAccess)); AddLinkFileInfo(pParent, RS_ModifyDT, DateTimeToStr(dtModify)); end; Guard(fs, TFileStream.Create(sPath, fmOpenReadWrite or fmShareDenyNone)); Result := LoadFromStream(fs, false); except // .. end; end; end.