#!/usr/bin/perl

use strict;
use warnings;
use FileHandle;

sub read_file($)
{
	my ($file) = @_;
	my $contents;
	my $len;

	open(FD, "<$file") || die $file;
	binmode FD;
	my @st = stat(FD);
	die $file if (!@st);
	$len = read(FD, $contents, $st[7]) || die $file;
	close(FD) || die $file;
	die "$file: Wanted length ", $st[7], ", got ", $len, "\n"
		if ($len != $st[7]);
	return $contents;
}

sub write_file($)
{
	my ($file, $contents) = @_;

	open(FD, ">$file") || die $file;
	binmode FD;
	print FD $contents;
	close(FD) || die $file;
}

if ($#ARGV != 2) {
	print "Usage: set-linker-version.pl <efi file> <minor linker version> <checksum hex>\n";
	print "  try \"objdump -p <efi file>\" to get the information\n";
	exit;
}

my $input = $ARGV[0];
my $minor_linker_version = $ARGV[1];
my $checksum = hex($ARGV[2]);
my $packed;

my $pe_image = read_file($input);

# Match e_magic. it must be 'M''Z'
my($e_magic) = unpack("v", substr($pe_image, 0, 2));
die "not a EFI Image\n" unless ($e_magic == 0x5A4D);

my($e_lfanew) = unpack("V", substr($pe_image, 60, 4));

# Match Signature 'P''E''\0''\0'
my($Signature) = unpack("V", substr($pe_image, $e_lfanew, 4));
die "not a PE Image\n" unless ($Signature == 0x4550);

# Check the pe header magic
my($pe_magic) = unpack("v", substr($pe_image, $e_lfanew+24, 2));
die "Not a PE32+ image\n" unless ($pe_magic == 0x20b);

# The offset of the optional header: $e_lfanew + 24
my $opthdr_offset = $e_lfanew + 24;

# Optional Header Standard Fields for PE32+
#   Standard fields
#     UINT16  Magic;
#     UINT8   MajorLinkerVersion;
#     UINT8   MinorLinkerVersion;
#     UINT32  SizeOfCode;
#     UINT32  SizeOfInitializedData;
#     UINT32  SizeOfUninitializedData;
#     UINT32  AddressOfEntryPoint;
#     UINT32  BaseOfCode;
#     UINT64  ImageBase;
#     UINT32  SectionAlignment;
#     UINT32  FileAlignment;
#     UINT16  MajorOperatingSystemVersion;
#     UINT16  MinorOperatingSystemVersion;
#     UINT16  MajorImageVersion;
#     UINT16  MinorImageVersion;
#     UINT16  MajorSubsystemVersion;
#     UINT16  MinorSubsystemVersion;
#     UINT32  Win32VersionValue;
#     UINT32  SizeOfImage;
#     UINT32  SizeOfHeaders;
#     UINT32  CheckSum;
#     UINT16  Subsystem;
#     UINT16  DllCharacteristics;
#     UINT64  SizeOfStackReserve;
#     UINT64  SizeOfStackCommit;
#     UINT64  SizeOfHeapReserve;
#     UINT64  SizeOfHeapCommit;
#     UINT32  LoaderFlags;
#     UINT32  NumberOfRvaAndSizes;

# The offset fo MinorLinkerVersion: $opthdr_offset + 2 + 1
$packed = pack("C", $minor_linker_version);
substr($pe_image, $opthdr_offset + 3, 1, $packed);

# The offset of CheckSum: $opthdr_offset + 2 + 1*2 + 4*5 + 8 + 4*2 + 2*6 + 4*3
$packed = pack("V", $checksum);
substr($pe_image, $opthdr_offset + 64, 4, $packed);

&write_file($input, $pe_image);
