739 lines
31 KiB
Plaintext
739 lines
31 KiB
Plaintext
{*******************************************************}
|
|
{ }
|
|
{ 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<PLfiEnt>;
|
|
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.
|