{*******************************************************}
{                                                       }
{       Tocsg.Prefetch                                  }
{                                                       }
{       Copyright (C) 2022 kku                          }
{                                                       }
{*******************************************************}

unit Tocsg.Prefetch;

interface

uses
  Tocsg.Obj, System.SysUtils, System.Classes, Winapi.Windows,
  System.Generics.Collections;

const
  PF_VER_1 = 17;
  PF_VER_2 = 23;
  PF_VER_3 = 26;
  PF_VER_4 = 30;

type
  TPfHeader = record
    dwVer: DWORD;
    arrSig: array[0..3] of AnsiChar;
    dwUnknown1,
    dwFSize: DWORD;
    arrFName: array[0..29] of Char; // array[0..59] of AnsiChar;
    dwPfHash,
    dwUnknown2: DWORD;
  end;

  TPfFileInfoVer1 = record
    dwOffset,
    dwEntCnt,
    dwOffsetTrace,
    dwEntCntTrace,
    dwOffsetFName,
    dwFNameSize,
    dwOffsetVolInfo,
    dwVolNum,
    dwVolSize: DWORD;
    ftLastExeTime: TFileTime;
    arrUnknown1: array [0..15] of AnsiChar;
    dwExeCounter: DWORD;
    dwUnknown2: DWORD;
  end;

  TPfFileInfoVer2 = record
    dwOffset,
    dwEntCnt,
    dwOffsetTrace,
    dwEntCntTrace,
    dwOffsetFName,
    dwFNameSize,
    dwOffsetVolInfo,
    dwVolNum,
    dwVolSize: DWORD;
    arrUnknown1: array [0..7] of AnsiChar;
    ftLastExeTime: TFileTime;
    arrUnknown2: array [0..15] of AnsiChar;
    dwExeCounter: DWORD;
    arrUnknown3: array [0..83] of AnsiChar;
  end;

  TPfFileInfoVer34 = record
    dwOffset,
    dwEntCnt,
    dwOffsetTrace,
    dwEntCntTrace,
    dwOffsetFName,
    dwFNameSize,
    dwOffsetVolInfo,
    dwVolNum,
    dwVolSize: DWORD;
    arrUnknown1: array [0..7] of AnsiChar;
    arrLastExeTime: array [0..7] of TFileTime;
    arrUnknown2: array [0..15] of AnsiChar;
    dwExeCounter: DWORD;
    arrUnknown3: array [0..95] of AnsiChar;
  end;

  TAssocFileEntVer1 = record
    dwStartTime,
    dwDuration,
    dwFNameOffset,
    dwFNameLen,
    dwUnknown: DWORD;
  end;

  TAssocFileEntVer234 = record
    dwStartTime,
    dwDuration,
    dwAverageDuration,
    dwFNameOffset,
    dwFNameLen,
    dwUnknown: DWORD;
    arrRefNTFS: array [0..7] of AnsiChar;
  end;

  TTgPrefetchAnal = class(TTgObject)
  private
    ms_: TMemoryStream;
    PfHeader_: TPfHeader;
    nRunCnt_: Integer;
    ExeDtList_: TList<TDateTime>;
    AssocFList_: TStringList;
    sPfPath_,
    sPath_,
    sFName_: String;
    function GetPfHeader(aStream: TStream): Boolean;
    procedure ExtrPfInfoVer1;
    procedure ExtrPfInfoVer2;
    procedure ExtrPfInfoVer34;
    function GetRunCount: Integer;
  public
    Constructor Create;
    Destructor Destroy; override;

    function LoadFromStream(aStream: TStream): Boolean;
    function LoadFromFile(sPath: String): Boolean;

    procedure Clear;

    function GetExeDateTimeToText(sDm: String = ''; sDtFormat: String = ''): String;
    function GetExeDateEnum: TEnumerator<TDateTime>;

    property RunCount: Integer read GetRunCount;
    property ExeDtList: TList<TDateTime> read ExeDtList_;

    property FilePath: String read sPath_;
    property FileName: String read sFName_;
  end;

implementation

uses
  Tocsg.Safe, Tocsg.Exception, Tocsg.NTDLL.Decompress, Tocsg.DateTime,
  Tocsg.Strings, System.Math, Tocsg.Path;

{ TTgPrefetchAnal }

Constructor TTgPrefetchAnal.Create;
begin
  Inherited Create;
  sPfPath_ := '';
  sPath_ := '';
  sFName_ := '';
  ms_ := nil;
  ExeDtList_ := TList<TDateTime>.Create;
  AssocFList_ := TStringList.Create;
end;

Destructor TTgPrefetchAnal.Destroy;
begin
  FreeAndNil(AssocFList_);
  FreeAndNil(ExeDtList_);
  if ms_ <> nil then
    FreeAndNil(ms_);
  Inherited;
end;

function TTgPrefetchAnal.GetPfHeader(aStream: TStream): Boolean;
var
  RtlHeader: TRtlHeader;
  pSrc, pBuf: TBytes;
  dwDecomLen: DWORD;
begin
  Result := false;
  if aStream.Size < SizeOf(PfHeader_) then
    exit;

  ZeroMemory(@PfHeader_, SizeOf(PfHeader_));
  aStream.Position := 0;

  ZeroMemory(@RtlHeader, SizeOf(RtlHeader));
  if aStream.Read(RtlHeader, SizeOf(RtlHeader)) <> SizeOf(RtlHeader) then
    exit;

  if not CompareMem(@RtlHeader.arrSig[0], @AnsiString('MAM')[1], 3) then
    exit;

  SetLength(pSrc, aStream.Size - aStream.Position);
  aStream.Read(pSrc[0], Length(pSrc));
  SetLength(pBuf, RtlHeader.dwSize);
  dwDecomLen := RtlDecompress(@pSrc[0], @pBuf[0], Length(pSrc), RtlHeader.dwSize, COMPRESSION_FORMAT_XPRESS_HUFF);
  if dwDecomLen > 0 then
  begin
    ms_ := TMemoryStream.Create;
    ms_.Write(pBuf[0], dwDecomLen);
    ms_.Position := 0;
    ms_.Read(PfHeader_, SizeOf(PfHeader_));
    if PfHeader_.arrSig = 'SCCA' then
      Result := true;
  end;
end;

procedure TTgPrefetchAnal.ExtrPfInfoVer1;
var
  FInfo: TPfFileInfoVer1;
begin
  if ms_ = nil then
    exit;

  ms_.Read(FInfo, SizeOf(FInfo));
  nRunCnt_ := FInfo.dwExeCounter;
  ExeDtList_.Add(ConvFileTimeToDateTime(FInfo.ftLastExeTime));
end;

procedure TTgPrefetchAnal.ExtrPfInfoVer2;
var
  FInfo: TPfFileInfoVer2;
begin
  if ms_ = nil then
    exit;

  ms_.Read(FInfo, SizeOf(FInfo));
  nRunCnt_ := FInfo.dwExeCounter;
  ExeDtList_.Add(ConvFileTimeToDateTime(FInfo.ftLastExeTime));
end;

procedure TTgPrefetchAnal.ExtrPfInfoVer34;
var
  FInfo: TPfFileInfoVer34;
  i: Integer;
  dwPos, dwEntSize,
  dwNameOffset: DWORD;
  AssocFileEnt: TAssocFileEntVer234;
  pBuf: TBytes;
  sPath: String;
begin
  if ms_ = nil then
    exit;

  ms_.Read(FInfo, SizeOf(FInfo));
  nRunCnt_ := FInfo.dwExeCounter;
  for i := Low(FInfo.arrLastExeTime) to High(FInfo.arrLastExeTime) do
    if (FInfo.arrLastExeTime[i].dwLowDateTime > 0) and (FInfo.arrLastExeTime[i].dwHighDateTime > 0) then
      ExeDtList_.Add(ConvFileTimeToDateTime_Local(FInfo.arrLastExeTime[i])); // 로컬 타임으로 가져옴 22_0905 15:54:59 kku
//      ExeDtList_.Add(ConvFileTimeToDateTime(FInfo.arrLastExeTime[i]));

  SetLength(pBuf, 1024);
  dwPos := FInfo.dwOffset;
  dwNameOffset := FInfo.dwOffsetFName;
  dwEntSize := SizeOf(AssocFileEnt);
  for i := 0 to FInfo.dwEntCnt - 1 do
    if dwPos < ms_.Size then
    begin
      ms_.Position := dwPos;
      if ms_.Read(AssocFileEnt, dwEntSize) <> dwEntSize then
        exit;
      dwPos := ms_.Position;

      with AssocFileEnt do
      begin
        if (dwNameOffset + dwFNameOffset + (dwFNameLen * 2)) < ms_.Size then
        begin
          ms_.Position := dwNameOffset + dwFNameOffset;
          ZeroMemory(pBuf, 1024);
          if ms_.Read(pBuf[0], dwFNameLen * 2) <> (dwFNameLen * 2) then
            exit;

          sPath := UpperCase(String(PChar(@pBuf[0])));
          if GetFileExt(sPath) = 'EXE' then
          begin
            sPath_ := ExtractFilePath(sPath);
            sFName_ := ExtractFileName(sPath);
          end;

          AssocFList_.Add(sPath);
        end;
      end;
    end;

end;

function TTgPrefetchAnal.LoadFromStream(aStream: TStream): Boolean;
begin
  Clear;
  Result := false;

  try
    if (aStream = nil) or (aStream.Size = 0) then
      exit;

    Result := GetPfHeader(aStream);
    if not Result then
      exit;

    case PfHeader_.dwVer of
      PF_VER_1 : ExtrPfInfoVer1;
      PF_VER_2 : ExtrPfInfoVer2;
      PF_VER_3,
      PF_VER_4 : ExtrPfInfoVer34;
    end;
  except
    on E: Exception do
      ETgException.TraceException(Self, E, 'Fail .. LoadFromStream()');
  end;
end;

function TTgPrefetchAnal.LoadFromFile(sPath: String): Boolean;
var
  fs: TFileStream;
begin
  Result := false;
  if not FileExists(sPath) then
    exit;

  try
    sPfPath_ := sPath;
    Guard(fs, TFileStream.Create(sPath, fmOpenRead));
    Result := LoadFromStream(fs);
  except
    on E: Exception do
      ETgException.TraceException(Self, E, 'Fail .. LoadFromFile()');
  end;
end;

procedure TTgPrefetchAnal.Clear;
begin
  if ms_ <> nil then
    FreeAndNil(ms_);
  Finalize(PfHeader_);
  ZeroMemory(@PfHeader_, SizeOf(PfHeader_));
  nRunCnt_ := 0;
  ExeDtList_.Clear;
  AssocFList_.Clear;
  sPfPath_ := '';
  sFName_ := '';
  sPath_ := '';
end;


function TTgPrefetchAnal.GetRunCount: Integer;
begin
  Result := Max(nRunCnt_, ExeDtList_.Count);
end;

function TTgPrefetchAnal.GetExeDateTimeToText(sDm: String = ''; sDtFormat: String = ''): String;
var
  i: Integer;
begin
  Result := '';
  try
    if sDm = '' then
      sDm := ',';
    for i := 0 to ExeDtList_.Count - 1 do
      if sDtFormat <> '' then
        SumString(Result, FormatDateTime(sDtFormat, ExeDtList_[i]), sDm)
      else
        SumString(Result, DateTimeToStr(ExeDtList_[i]), sDm);
  except
    on E: Exception do
      ETgException.TraceException(Self, E, 'Fail .. GetExeDateTimeToCommaText()');
  end;
end;

function TTgPrefetchAnal.GetExeDateEnum: TEnumerator<TDateTime>;
begin
  Result := ExeDtList_.GetEnumerator;
end;

end.
