#!/usr/bin/perl -w
#
# Quick Perl program to decode and display details about 
# tagged images created by mknbi
# -e extracts directory to `nbidir' and segments to `segmentN'
# N = 0, 1, 2, ...
#
# Added code to dump vendor tag in hex (for DOS disk parameters)
#
# Ken Yap, August 1999
#

use strict;

use vars qw($imagefile $data $curoffset $dirfile @seglengths $vendordata
	$extract $status $i);

sub getvendordata ($)
{
	my ($flags) = @_;

	my $vendordata = '';
	my $vendorlen = ($flags & 0xff) >> 4;
	if ($vendorlen > 0) {
		$vendorlen *= 4;
		$vendordata = unpack("a$vendorlen", substr($data, $curoffset));
		$curoffset += $vendorlen;
	}
	return ($vendordata);
}

sub decodesegmentflags ($)
{
	my ($flags) = @_;
	my ($type);

	$flags >>= 24;
	$flags &= 0x3;
	($flags == 0) and $type = "Absolute";
	($flags == 1) and $type = "Follows last segment";
	($flags == 2) and $type = "Below end of memory";
	($flags == 3) and $type = "Below last segment loaded";
	return ($type);
}

sub one_nbi_segment ($)
{
	my ($segnum) = @_;
	my ($type, $vendordata, @vdata, $i);

	my ($flags, $loadaddr, $imagelen, $memlength) = unpack('V4', substr($data, $curoffset));
	$curoffset += 16;
	printf "Segment number %d\n", $segnum;
	printf "Load address:\t\t%08x\n", $loadaddr;
	printf "Image length:\t\t%d\n", $imagelen;
	printf "Memory length:\t\t%d\n", $memlength;
	$type = &decodesegmentflags($flags);
	print "Position:\t\t$type\n";
	printf "Vendor tag:\t\t%d\n", ($flags >> 8) & 0xff;
	if (($vendordata = &getvendordata($flags)) ne '') {
		print "Vendor data:\t\t\"", $vendordata, "\"\n";
		@vdata = unpack('C*', $vendordata);
		print "Vendor data in hex:\t";
		foreach $i (0..$#vdata) {
			printf "%02x ", $vdata[$i];
		}
		print "\n";
	}
	print "\n";
	push (@seglengths, $imagelen);
	return (($flags >> 26) & 1);
}

sub decode_nbi
{
	my ($magic, $flags, $bx, $ds, $ip, $cs) = unpack('a4Vv4', substr($data, 0, 16));

	$curoffset = 16;
	# Decode the header
	printf "Type: NBI\nHeader location:\t%04x:%04x\n", $ds, $bx;
	if (($flags >> 31) & 1) {
		printf "Start address:\t\t%04x%04x (flat)\n", $cs, $ip;
	} else {
		printf "Start address:\t\t%04x:%04x\n", $cs, $ip;
	}
	print "Flags:\n";
		print "\tReturn to loader after execution (extension)\n" if (($flags >> 8) &  1);
	if (($vendordata = &getvendordata($flags)) ne '') {
		print "Vendor data:\t\t", $vendordata, "\n";
	}
	print "\n";

	# Now decode each segment record
	my $segnum = 0;
	do {
		$i = &one_nbi_segment($segnum);
		++$segnum;
	} while (!$i);
}

sub one_elf_segment ($$)
{
	my ($segnum, $curoffset) = @_;

	my ($offset, $vaddr, $paddr, $filesz, $memsz, $flags,
		$align) = unpack('@4V6', substr($data, $curoffset));
	printf "Segment number %d\n", $segnum;
	printf "Load address:\t\t%08x\n", $vaddr;
	printf "Image length:\t\t%d\n", $filesz;
	printf "Memory length:\t\t%d\n", $memsz;
	print "\n";
	push (@seglengths, $filesz);
}

sub decode_elf
{
	my ($entry, $phoff, $shoff, $flags, $ehsize, $phentsize,
		$phnum) = unpack('@24V4v3', $data);
	printf "Type: ELF\nStart address:\t\t%08x\n", $entry;
	print "Flags:\n";
		print "\tReturn to loader after execution (extension)\n" if ($flags & 0x8000000);
	print "\n";
	$curoffset = $phoff;
	foreach $i (0..$phnum-1) {
		&one_elf_segment($i, $curoffset);
		$curoffset += $phentsize;
	}
}

$extract = 0;
@seglengths = ();
$#ARGV >= 0 or die "Usage: disnbi [-e] Etherboot-image-file\n";
if ($ARGV[0] eq '-e') {
	$extract = 1; shift
}
$#ARGV >= 0 or die "Usage: disnbi [-e] Etherboot-image-file\n";
$imagefile= $ARGV[0];
open(I, $ARGV[0]) or die "$imagefile: $!\n";
binmode(I);
(defined($status = sysread(I, $data, 512)) and $status == 512)
	or die "$imagefile: Cannot read header\n";
my ($magic) = unpack('a4', substr($data, 0, 4));
if ($magic eq "\x36\x13\x03\x1B") {
	&decode_nbi();
	$dirfile = 'nbidir';
} elsif ($magic eq "\x7FELF") {
	&decode_elf();
	$dirfile = 'elfdir';
} else {
	die "$imagefile: Not a tagged or ELF image file\n";
}

exit(0) if ($extract == 0);
print "Dumping directory to `$dirfile'...\n";
open(O, ">$dirfile") or die "$dirfile: $!\n";
binmode(O);
print O $data;
close(O);
$data = '';
foreach $i (0..$#seglengths) {
	print "Extracting segment $i to `segment$i'...\n";
	open(O, ">segment$i") or die "segment$i: $!\n";
	binmode(O);
	(defined($status = sysread(I, $data, $seglengths[$i]))
		and $status = $seglengths[$i])
		or die "$imagefile: Cannot read data\n";
	print O $data;
	close(O);
}
print "Done\n";
exit(0);

__END__

=head1 NAME

disnbi - display Etherboot image

=head1 SYNOPSIS

B<disnbi> [C<-e>] I<Etherboot-file>

=head1 DESCRIPTION

B<disnbl> is a program that to display in symbolic form the contents
of a Etherboot image created by mknbi or mkelf. Detection of image type
is automatic.

B<-e> Extract contents of image as well. The directory will be written
to C<nbidir> or C<elfdir> and the segments to C<segment>I<n> where I<n>
is 0, 1, 2, etc.

=head1 BUGS

Please report all bugs to the author.

=head1 SEE ALSO

Etherboot tutorial at C<http://etherboot.sourceforge.net/>

=head1 COPYRIGHT

B<disnbl> is under the GNU Public License

=head1 AUTHOR

Ken Yap (C<ken_yap@users.sourceforge.net>)

=head1 DATE

Version 1.4 December 2002
