{*******************************************************} { } { 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) 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) 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.