{$mode objfpc}
{$apptype console}

program exe2kos;

uses
  SysUtils, Classes, ExeTypes, KosTypes;

const
  ARGS_SIZE = 512;
  PATH_SIZE = 512;

type
  EExeError = class(Exception);

  TExeImage = class;
  TExeImageSection = class;

  TExeImageSection = class
  private
    FExeImage: TExeImage;
    FHeader: IMAGE_SECTION_HEADER;
    FName: String;
    procedure Read(var Buffer);
  public
    constructor Create(AExeImage: TExeImage; APosition: DWord);
    property Name: String read FName;
    property VirtualSize: DWord read FHeader.PhysicalAddress;
    property SectionRVA: DWord read FHeader.VirtualAddress;
    property PhysicalSize: DWord read FHeader.SizeOfRawData;
    property PhysicalOffset: DWord read FHeader.PointerToRawData;
    property ObjectFlags: DWord read FHeader.Characteristics;
  end;

  TExeImageSections = class
  private
    FCount: DWord;
    FItems: array of TExeImageSection;
    function GetItem(Index: DWord): TExeImageSection;
  public
    constructor Create(AExeImage: TExeImage; APosition, ACount: DWord);
    destructor Destroy; override;
    function ByName(AName: String): TExeImageSection;
    property Count: DWord read FCount;
    property Items[Index: DWord]: TExeImageSection read GetItem; default;
  end;

  TExeImage = class
  private
    FFileName: String;
    FFileStream: TStream;
    FDosHeader: IMAGE_DOS_HEADER;
    FNTHeader: IMAGE_NT_HEADERS;
    FSections: TExeImageSections;
    procedure Read(var Buffer; Position, Size: Longint);
    function GetSizeOfCode(): DWord;
    function GetSizeOfInitData(): DWord;
    function GetSizeOfUnInitData(): DWord;
    function GetEntryPoint(): DWord;
    function GetImageBase(): DWord;
    function GetObjectAlign(): DWord;
    function GetFileAlign(): DWord;
    function GetImageSize(): DWord;
    function GetHeaderSize(): DWord;
    function GetStackReserveSize(): DWord;
    function GetStackCommitSize(): DWord;
    function GetHeapReserveSize(): DWord;
    function GetHeapCommitSize(): DWord;
  public
    constructor Create(AFileName: String);
    destructor Destroy; override;
    property FileName: String read FFileName;
    property Sections: TExeImageSections read FSections;
    property SizeOfCode: DWord read GetSizeOfCode;
    property SizeOfInitializedData: DWord read GetSizeOfInitData;
    property SizeOfUninitializedData: DWord read GetSizeOfUnInitData;
    property EntryPoint: DWord read FNTHeader.OptionalHeader.AddressOfEntryPoint{GetEntryPoint};
    property ImageBase: DWord read FNTHeader.OptionalHeader.ImageBase{GetImageBase};
    property ObjectAlign: DWord read GetObjectAlign;
    property FileAlign: DWord read GetFileAlign;
    property ImageSize: DWord read GetImageSize;
    property HeaderSize: DWord read GetHeaderSize;
    property StackReserveSize: DWord read GetStackReserveSize;
    property StackCommitSize: DWord read GetStackCommitSize;
    property HeapReserveSize: DWord read GetHeapReserveSize;
    property HeapCommitSize: DWord read GetHeapCommitSize;
  end;


constructor TExeImage.Create(AFileName: String);
begin
  FFileName := AFileName;
  FFileStream := TFileStream.Create(FFileName, fmOpenRead);

  Read(FDosHeader, 0, SizeOf(FDosHeader));
  if not FDosHeader.e_magic = IMAGE_DOS_SIGNATURE then
    EExeError.Create('Unrecognized file format');

  Read(FNTHeader, FDosHeader.e_lfanew, SizeOf(FNTHeader));
  if FNTHeader.Signature <> IMAGE_NT_SIGNATURE then
    EExeError.Create('Not a PE (WIN32 Executable) file');

  FSections := TExeImageSections.Create(Self,
    FDosHeader.e_lfanew + SizeOf(FNTHeader), FNTHeader.FileHeader.NumberOfSections);
end;

destructor TExeImage.Destroy;
begin
  FSections.Free;
  FFileStream.Free;
end;

procedure TExeImage.Read(var Buffer; Position, Size: Longint);
begin
  FFileStream.Position := Position;
  if FFileStream.Read(Buffer, Size) <> Size then
    EExeError.Create('Damaged or unrecognized file');
end;

function TExeImage.GetSizeOfCode(): DWord;
begin
  Result := FNTHeader.OptionalHeader.SizeOfCode;
end;

function TExeImage.GetSizeOfInitData(): DWord;
begin
  Result := FNTHeader.OptionalHeader.SizeOfInitializedData;
end;

function TExeImage.GetSizeOfUnInitData(): DWord;
begin
  Result := FNTHeader.OptionalHeader.SizeOfUninitializedData;
end;

function TExeImage.GetEntryPoint(): DWord;
begin
  Result := FNTHeader.OptionalHeader.AddressOfEntryPoint;
end;

function TExeImage.GetImageBase(): DWord;
begin
  Result := FNTHeader.OptionalHeader.ImageBase;
end;

function TExeImage.GetObjectAlign(): DWord;
begin
  Result := FNTHeader.OptionalHeader.SectionAlignment;
end;

function TExeImage.GetFileAlign(): DWord;
begin
  Result := FNTHeader.OptionalHeader.FileAlignment;
end;

function TExeImage.GetImageSize(): DWord;
begin
  Result := FNTHeader.OptionalHeader.SizeOfImage;
end;

function TExeImage.GetHeaderSize(): DWord;
begin
  Result := FNTHeader.OptionalHeader.SizeOfHeaders;
end;

function TExeImage.GetStackReserveSize(): DWord;
begin
  Result := FNTHeader.OptionalHeader.SizeOfStackReserve;
end;

function TExeImage.GetStackCommitSize(): DWord;
begin
  Result := FNTHeader.OptionalHeader.SizeOfStackCommit;
end;

function TExeImage.GetHeapReserveSize(): DWord;
begin
  Result := FNTHeader.OptionalHeader.SizeOfHeapReserve;
end;

function TExeImage.GetHeapCommitSize(): DWord;
begin
  Result := FNTHeader.OptionalHeader.SizeOfHeapCommit;
end;


constructor TExeImageSections.Create(AExeImage: TExeImage; APosition, ACount: DWord);
var
  i: Integer;
begin
  FCount := ACount;
  SetLength(FItems, ACount);
  for i := 0 to ACount - 1 do
  begin
    FItems[i] := TExeImageSection.Create(AExeImage, APosition);
    Inc(APosition, SizeOf(IMAGE_SECTION_HEADER));
  end;
end;

destructor TExeImageSections.Destroy;
var
  i: Integer;
begin
  for i := 0 to Length(FItems) - 1 do FItems[i].Free;
  SetLength(FItems, 0);
end;

function TExeImageSections.GetItem(Index: DWord): TExeImageSection;
begin
  Result := FItems[Index];
end;

function TExeImageSections.ByName(AName: String): TExeImageSection;
var
  i: Integer;
begin
  for i := 0 to Length(FItems) - 1 do
  if FItems[i].Name = AName then
  begin
    Result := FItems[i];
    Exit;
  end;
  Result := nil;
end;



constructor TExeImageSection.Create(AExeImage: TExeImage; APosition: DWord);
begin
  FExeImage := AExeImage;
  FExeImage.Read(FHeader, APosition, SizeOf(FHeader));
  FName := FHeader.Name;
end;

procedure TExeImageSection.Read(var Buffer);
begin
  FExeImage.Read(Buffer, PhysicalOffset, PhysicalSize);
end;


procedure WriteHead(s: String);
var
  i: Integer;
begin
  WriteLn;
  WriteLn(s);
  for i:=1 to Length(s) do Write('-');
  WriteLn;
end;

procedure Convert(InputFile, OutputFile: String);
var
  ExeImage: TExeImage;
  KosHeader: TKosHeader;
  FileStream: TStream;
  ImageBase, ImageSize, Size: DWord;
  Buffer: Pointer;
  i: Integer;
begin
  ExeImage := TExeImage.Create(InputFile);
  WriteHead('NT Header');
  WriteLn(Format('Size of Code: %d', [ExeImage.SizeOfCode]));
  WriteLn(Format('Size of Init Data: %d', [ExeImage.SizeOfInitializedData]));
  WriteLn(Format('Size of UnInit Data: %d', [ExeImage.SizeOfUninitializedData]));
  WriteLn(Format('Entry Point: 0x%x', [ExeImage.EntryPoint]));
  WriteLn(Format('Image Base: 0x%x', [ExeImage.ImageBase]));
  WriteLn(Format('Object Align: %d; File Align: %d', [ExeImage.ObjectAlign, ExeImage.FileAlign]));
  WriteLn(Format('Image Size: %d; Header Size: %d', [ExeImage.ImageSize, ExeImage.HeaderSize]));
  WriteLn(Format('Stack Reserve Size: %d; Stack Commit Size: %d', [ExeImage.StackReserveSize, ExeImage.StackCommitSize]));
  WriteLn(Format('Heap Reserve Size: %d; Heap Comit Size: %d', [ExeImage.HeapReserveSize, ExeImage.HeapCommitSize]));

  ImageBase := ExeImage.ImageBase;
  ImageSize := 0;

  { запись секций }
  FileStream := TFileStream.Create(OutputFile, fmCreate);
  for i:=0 to ExeImage.Sections.Count-1 do
  with ExeImage.Sections[i] do
  begin
    WriteHead(Format('Section %s (0x%x)', [Name, ObjectFlags]));
    WriteLn(Format('Section RVA/Size: 0x%x / %d', [SectionRVA, VirtualSize]));
    WriteLn(Format('Physical Offset/Size: 0x%x / %d', [PhysicalOffset, PhysicalSize]));

    Size := ImageBase + SectionRVA;
    FileStream.Position := Size;
    Inc(Size, VirtualSize);
    if Size > ImageSize then ImageSize := Size;

    if PhysicalSize > 0 then
    begin
      GetMem(Buffer, PhysicalSize);
      Read(Buffer^);
      FileStream.Write(Buffer^, PhysicalSize);
      FreeMem(Buffer);
    end;
    {if VirtualSize - PhysicalSize > 0 then
    begin
      GetMem(Buffer, VirtualSize - PhysicalSize);
      FillByte(Buffer^, VirtualSize - PhysicalSize, 0);
      FileStream.Write(Buffer^, VirtualSize - PhysicalSize);
      FreeMem(Buffer);
    end;}
  end;

  FillByte(KosHeader, SizeOf(KosHeader), 0);
  with KosHeader do
  begin
    sign    := KOS_SIGN;
    version := 1;
    start   := ImageBase + ExeImage.EntryPoint;
    size    := FileStream.Size;
    args    := ImageSize;
    path    := args + ARGS_SIZE;
    stack   := path + PATH_SIZE + ExeImage.StackReserveSize;
    memory  := stack;
  end;
  FileStream.Position := 0;
  FileStream.Write(KosHeader, SizeOf(KosHeader));
  FileStream.Free();
end;


var
  InputFile, OutputFile: String;
begin
  if ParamCount < 1 then
  begin
    WriteLn(Format('%s <exe input file> [kos output file]', [ExtractFileName(ParamStr(0))]));
    Exit;
  end;

  InputFile := ParamStr(1);
  if ParamCount <2 then
    OutputFile := ChangeFileExt(InputFile, '') else
    OutputFile := ParamStr(2);

  if InputFile = OutputFile then
    WriteLn(Format('Cannot convert the file "%s" onto itself.', [InputFile])) else

  if not FileExists(InputFile) then
    WriteLn(Format('Input the file "%s", not found.', [InputFile])) else

  begin
    WriteLn(Format('Converting "%s" to "%s"...', [InputFile, OutputFile]));
    Convert(InputFile, OutputFile);
  end;
end.
