406 lines
12 KiB
Plaintext
406 lines
12 KiB
Plaintext
{*******************************************************}
|
||
{ }
|
||
{ ProcessJumpList }
|
||
{ }
|
||
{ Copyright (C) 2025 kku }
|
||
{ }
|
||
{*******************************************************}
|
||
|
||
unit ProcessJumpList;
|
||
|
||
interface
|
||
|
||
uses
|
||
Tocsg.Obj, System.Classes, System.SysUtils, Winapi.Windows,
|
||
System.Generics.Collections;
|
||
|
||
type
|
||
PJmpEnt = ^TJmpEnt;
|
||
TJmpEnt = record
|
||
sName: String;
|
||
dwType: DWORD;
|
||
llSize: LONGLONG;
|
||
Stream: TStream;
|
||
end;
|
||
TJmpEntList = class(TList<PJmpEnt>)
|
||
protected
|
||
procedure Notify(const Item: PJmpEnt; Action: TCollectionNotification); override;
|
||
end;
|
||
|
||
TJmpDestInfo = packed record
|
||
dwVersion: DWORD;
|
||
arrUnknown: array [0..27] of Byte;
|
||
end;
|
||
|
||
TJmpDestH = packed record
|
||
sCheckSum: array [0..7] of AnsiChar; // 가리키고 있는 Stream의 Check Sum 값이 저장되어 있다.
|
||
VolDroidId, // Volume Droid ID
|
||
FileDroidId, // File Droid ID
|
||
BrhVolDroidId, // Birth volume Droid ID
|
||
BrhFileDroidId: TGUID; // Birth file Droid ID
|
||
sHostName: array [0..15] of AnsiChar; // 해당 Stream이 생성 된 컴퓨터 이름이 저장 된다.
|
||
end;
|
||
|
||
PJmpDestM78 = ^TJmpDestM78; // Windows 7, 8 용 중간 데이터
|
||
TJmpDestM78 = packed record
|
||
llSeq: LONGLONG; // 가리키고 있는 Stream의 이름이 저장 된다.
|
||
fCount: Single; // Stream의 접근 횟수를 부동소수점으로 표현한 값을 저장하고 있다.
|
||
ftLastAccess: TFileTime; // Stream의 마지막 수정 시간을 저장하고 있다.
|
||
end;
|
||
|
||
PJmpDestM10 = ^TJmpDestM10; // Windows 10 용 중간 데이터
|
||
TJmpDestM10 = packed record
|
||
dwSeq: DWORD;
|
||
llUnDefined: LONGLONG; // In all test ‘0x00 0x00 0x00 0x00’
|
||
ftLastAccess: TFileTime; // Stream의 마지막 수정 시간을 저장하고 있다.
|
||
end;
|
||
|
||
TJmpDestT78 = packed record // Windows 7, 8 용 끝 데이터
|
||
dwPin: DWORD;
|
||
wUniStrLen: WORD;
|
||
end;
|
||
|
||
TJmpDestT10 = packed record // Windows 10 용 끝 데이터
|
||
dwPin: DWORD;
|
||
dwUnDefined1: DWORD; // In all tests, ‘0xFF 0xFF 0xFF 0xFF’
|
||
dwCount: DWORD;
|
||
llUnDefined2: LONGLONG; // In all test ‘0x00 0x00 0x00 0x00’
|
||
wUniStrLen: WORD;
|
||
end;
|
||
|
||
// 윈도우 7지원을 위해 밑에 정보를 나눔
|
||
TJmpDestHeader = packed record
|
||
sHostName: String;
|
||
llSeq: LONGLONG; // 가리키고 있는 Stream의 이름이 저장 된다.
|
||
fCount: Single; // Stream의 접근 횟수를 부동소수점으로 표현한 값을 저장하고 있다.
|
||
ftLastAccess: TFileTime; // Stream의 마지막 수정 시간을 저장하고 있다.
|
||
end;
|
||
|
||
PJmpDestEnt = ^TJmpDestEnt;
|
||
TJmpDestEnt = record
|
||
Header: TJmpDestHeader;
|
||
sData: String;
|
||
end;
|
||
TJmpDestEntList = class(TList<PJmpDestEnt>)
|
||
protected
|
||
procedure Notify(const Item: PJmpDestEnt; Action: TCollectionNotification); override;
|
||
end;
|
||
|
||
// 사용하지 않음. 만들다 말음 25_1216 14:21:17 kku
|
||
// GetLastOpenFileFromJumpListAuto() 이거만 사용
|
||
TJumpListAuto = class(TTgObject)
|
||
private
|
||
JmpEntList_: TJmpEntList;
|
||
JmpDestEntList_: TJmpDestEntList;
|
||
public
|
||
Constructor Create;
|
||
Destructor Destroy; override;
|
||
|
||
procedure LoadFromFile(sPath: String);
|
||
|
||
property JmpEntList: TJmpEntList read JmpEntList_;
|
||
property JmpDestEntList: TJmpDestEntList read JmpDestEntList_;
|
||
end;
|
||
|
||
// 점프리스트 파일에서 마지막 열어본 파일 경로 가져오기 25_1216 14:21:47 kku
|
||
// Tocsg.Files.pas에 추가함
|
||
function GetLastOpenFileFromJumpListAuto(const sJmpAutoPath: String): String;
|
||
|
||
implementation
|
||
|
||
uses
|
||
EM.GSStorage, Tocsg.Path, Tocsg.Safe, Tocsg.Exception;
|
||
|
||
const
|
||
LinkSig: array [0..11] of Byte = ($4C, $00, $00, $00, $01, $14,
|
||
$02, $00, $00, $00, $00, $00);
|
||
EndFileSig: array [0..3] of Byte = ($AB, $FB, $BF, $BA);
|
||
|
||
{ TJmpEntList }
|
||
|
||
procedure TJmpEntList.Notify(const Item: PJmpEnt; Action: TCollectionNotification);
|
||
begin
|
||
case Action of
|
||
cnAdded: ;
|
||
cnRemoved:
|
||
begin
|
||
if Item.Stream <> nil then
|
||
Item.Stream.Free;
|
||
Dispose(Item);
|
||
end;
|
||
cnExtracted: ;
|
||
end;
|
||
end;
|
||
|
||
{ TJmpDestEntList }
|
||
|
||
procedure TJmpDestEntList.Notify(const Item: PJmpDestEnt; Action: TCollectionNotification);
|
||
begin
|
||
case Action of
|
||
cnAdded: ;
|
||
cnRemoved: Dispose(Item);
|
||
cnExtracted: ;
|
||
end;
|
||
end;
|
||
|
||
{ TJumpListAuto }
|
||
|
||
Constructor TJumpListAuto.Create;
|
||
begin
|
||
Inherited Create;
|
||
JmpEntList_ := TJmpEntList.Create;
|
||
JmpDestEntList_ := TJmpDestEntList.Create;
|
||
end;
|
||
|
||
Destructor TJumpListAuto.Destroy;
|
||
begin
|
||
FreeAndNil(JmpDestEntList_);
|
||
FreeAndNil(JmpEntList_);
|
||
Inherited;
|
||
end;
|
||
|
||
function CreateJmpEnt(sName: String; dwType: DWORD): PJmpEnt;
|
||
begin
|
||
New(Result);
|
||
ZeroMemory(Result, SizeOf(TJmpEnt));
|
||
Result.Stream := TMemoryStream.Create;
|
||
Result.sName := sName;
|
||
Result.dwType := dwType;
|
||
end;
|
||
|
||
// 점프리스트에서 최근 열어본 파일 알아오기 위해 만듬
|
||
// 점프리스트에서 마지막으로 열어본 파일 가져오는데까지만 만들다 말음... 25_1216 14:20:38 kku
|
||
// GetLastOpenFileFromJumpListAuto()
|
||
procedure TJumpListAuto.LoadFromFile(sPath: String);
|
||
var
|
||
sExt: String;
|
||
dtCreate, dtModify, dtAccess: TDateTime;
|
||
llSize: LONGLONG;
|
||
stg: TGSStorage;
|
||
stgRoot: TGSStorageCursor;
|
||
enum: TGSStorageEnum;
|
||
i: Integer;
|
||
pEnt: PJmpEnt;
|
||
|
||
// LF: TParserLinkFile;
|
||
DestInfo: TJmpDestInfo;
|
||
DestH: TJmpDestH;
|
||
DestM: array [0..19] of Byte;
|
||
DestT78: TJmpDestT78;
|
||
DestT10: TJmpDestT10;
|
||
pDestE: PJmpDestEnt;
|
||
sTemp: array of Char;
|
||
wUniStrLen: WORD;
|
||
begin
|
||
try
|
||
JmpEntList_.Clear;
|
||
JmpDestEntList_.Clear;
|
||
if FileExists(sPath) then
|
||
begin
|
||
sExt := GetFileExt(sPath).ToUpper;
|
||
if sExt <> 'AUTOMATICDESTINATIONS-MS' then
|
||
exit;
|
||
|
||
Guard(stg, TGSStorage.Create);
|
||
|
||
if stg.OpenFile(sPath, false, stgRoot) <> S_OK then
|
||
exit;
|
||
|
||
if stgRoot.Enumerate(enum) <> S_OK then
|
||
exit;
|
||
|
||
try
|
||
for i := 0 to enum.Count - 1 do
|
||
begin
|
||
pEnt := CreateJmpEnt(enum.ElementEnum[i].pwcsName, enum.ElementEnum[i].dwType);
|
||
try
|
||
stgRoot.ReadStream(pEnt.sName, pEnt.Stream);
|
||
except
|
||
FreeAndNil(pEnt.Stream);
|
||
Dispose(pEnt);
|
||
continue;
|
||
end;
|
||
pEnt.llSize := pEnt.Stream.Size;
|
||
JmpEntList_.Add(pEnt);
|
||
|
||
pEnt.Stream.Position := 0;
|
||
if CompareText('DestList', pEnt.sName) = 0 then
|
||
begin
|
||
if pEnt.Stream.Read(DestInfo, SizeOf(DestInfo)) <> SizeOf(DestInfo) then
|
||
exit;
|
||
|
||
while pEnt.Stream.Size > pEnt.Stream.Position do
|
||
begin
|
||
if pEnt.Stream.Read(DestH, SizeOf(DestH)) <> SizeOf(DestH) then
|
||
break;
|
||
|
||
if pEnt.Stream.Read(DestM, 20) <> 20 then
|
||
break;
|
||
|
||
New(pDestE);
|
||
ZeroMemory(pDestE, SizeOf(TJmpDestEnt));
|
||
pDestE.Header.sHostName := AnsiString(DestH.sHostName);
|
||
|
||
// if PJmpDestM10(@DestM[0]).llUnDefined = 0 then
|
||
if DestInfo.dwVersion >= 4 then // Windows 7은 1, 10은 4로 보이는데...
|
||
begin
|
||
// Windows 10
|
||
pDestE.Header.llSeq := PJmpDestM10(@DestM[0]).dwSeq;
|
||
pDestE.Header.ftLastAccess := PJmpDestM10(@DestM[0]).ftLastAccess;
|
||
|
||
if pEnt.Stream.Read(DestT10, SizeOf(DestT10)) <> SizeOf(DestT10) then
|
||
break;
|
||
|
||
pDestE.Header.fCount := DestT10.dwCount;
|
||
wUniStrLen := DestT10.wUniStrLen;
|
||
end else begin
|
||
// Windows 7, 8
|
||
pDestE.Header.llSeq := PJmpDestM78(@DestM[0]).llSeq;
|
||
pDestE.Header.fCount := PJmpDestM78(@DestM[0]).fCount;
|
||
pDestE.Header.ftLastAccess := PJmpDestM78(@DestM[0]).ftLastAccess;
|
||
|
||
if pEnt.Stream.Read(DestT78, SizeOf(DestT78)) <> SizeOf(DestT78) then
|
||
break;
|
||
|
||
// if DestT78.dwPin <> $FFFFFFFF then
|
||
// break;
|
||
|
||
wUniStrLen := DestT78.wUniStrLen;
|
||
end;
|
||
|
||
if (wUniStrLen > 0) and (wUniStrLen <> WORD(-1)) then
|
||
begin
|
||
SetLength(sTemp, wUniStrLen + 1);
|
||
pEnt.Stream.Read(sTemp[0], wUniStrLen * 2);
|
||
sTemp[wUniStrLen] := #0;
|
||
pDestE.sData := PChar(@sTemp[0]);
|
||
|
||
if DestInfo.dwVersion > 1 then
|
||
pEnt.Stream.Position := pEnt.Stream.Position + 4;
|
||
end;
|
||
|
||
JmpDestEntList_.Add(pDestE);
|
||
end;
|
||
end else begin
|
||
// Guard(LF, TParserLinkFile.Create);
|
||
// LF.LoadFromStream(pEnt.Stream);
|
||
// LF.
|
||
end;
|
||
end;
|
||
finally
|
||
stgRoot.FreeMemAfterEnum(enum);
|
||
end;
|
||
end;
|
||
except
|
||
on E: Exception do
|
||
ETgException.TraceException(Self, E, 'Fail .. LoadFromFile()');
|
||
end;
|
||
end;
|
||
|
||
function GetLastOpenFileFromJumpListAuto(const sJmpAutoPath: String): String;
|
||
var
|
||
ms: TMemoryStream;
|
||
stg: TGSStorage;
|
||
stgRoot: TGSStorageCursor;
|
||
enum: TGSStorageEnum;
|
||
i: Integer;
|
||
|
||
DestInfo: TJmpDestInfo;
|
||
DestH: TJmpDestH;
|
||
DestM: array [0..19] of Byte;
|
||
DestT78: TJmpDestT78;
|
||
DestT10: TJmpDestT10;
|
||
sTemp: array of Char;
|
||
wUniStrLen: WORD;
|
||
begin
|
||
Result := '';
|
||
try
|
||
if FileExists(sJmpAutoPath) then
|
||
begin
|
||
if GetFileExt(sJmpAutoPath).ToUpper <> 'AUTOMATICDESTINATIONS-MS' then
|
||
exit;
|
||
|
||
Guard(stg, TGSStorage.Create);
|
||
if stg.OpenFile(sJmpAutoPath, false, stgRoot) <> S_OK then
|
||
exit;
|
||
|
||
if stgRoot.Enumerate(enum) <> S_OK then
|
||
exit;
|
||
|
||
try
|
||
for i := 0 to enum.Count - 1 do
|
||
begin
|
||
if CompareText('DestList', enum.ElementEnum[i].pwcsName) <> 0 then
|
||
continue;
|
||
|
||
Guard(ms, TMemoryStream.Create);
|
||
try
|
||
stgRoot.ReadStream(enum.ElementEnum[i].pwcsName, ms);
|
||
except
|
||
exit;
|
||
end;
|
||
|
||
if ms.Read(DestInfo, SizeOf(DestInfo)) <> SizeOf(DestInfo) then
|
||
exit;
|
||
|
||
while ms.Size > ms.Position do
|
||
begin
|
||
if ms.Read(DestH, SizeOf(DestH)) <> SizeOf(DestH) then
|
||
break;
|
||
|
||
if ms.Read(DestM, 20) <> 20 then
|
||
break;
|
||
|
||
if DestInfo.dwVersion >= 4 then // Windows 7은 1, 10은 4로 보이는데...
|
||
begin
|
||
// Windows 10
|
||
// Header.llSeq := PJmpDestM10(@DestM[0]).dwSeq;
|
||
// Header.ftLastAccess := PJmpDestM10(@DestM[0]).ftLastAccess;
|
||
|
||
if ms.Read(DestT10, SizeOf(DestT10)) <> SizeOf(DestT10) then
|
||
break;
|
||
|
||
// Header.fCount := DestT10.dwCount;
|
||
wUniStrLen := DestT10.wUniStrLen;
|
||
end else begin
|
||
// Windows 7, 8
|
||
// Header.llSeq := PJmpDestM78(@DestM[0]).llSeq;
|
||
// Header.fCount := PJmpDestM78(@DestM[0]).fCount;
|
||
// Header.ftLastAccess := PJmpDestM78(@DestM[0]).ftLastAccess;
|
||
|
||
if ms.Read(DestT78, SizeOf(DestT78)) <> SizeOf(DestT78) then
|
||
break;
|
||
|
||
// if DestT78.dwPin <> $FFFFFFFF then
|
||
// break;
|
||
|
||
wUniStrLen := DestT78.wUniStrLen;
|
||
end;
|
||
|
||
if (wUniStrLen > 0) and (wUniStrLen <> WORD(-1)) then
|
||
begin
|
||
SetLength(sTemp, wUniStrLen + 1);
|
||
ms.Read(sTemp[0], wUniStrLen * 2);
|
||
sTemp[wUniStrLen] := #0;
|
||
Result := PChar(@sTemp[0]);
|
||
|
||
if DestInfo.dwVersion > 1 then
|
||
ms.Position := ms.Position + 4;
|
||
|
||
exit;
|
||
end;
|
||
end;
|
||
end;
|
||
finally
|
||
stgRoot.FreeMemAfterEnum(enum);
|
||
end;
|
||
end;
|
||
except
|
||
on E: Exception do
|
||
ETgException.TraceException(E, 'Fail .. GetRecentOpenFileFromJumpListAuto()');
|
||
end;
|
||
end;
|
||
|
||
end.
|
||
|