#! /usr/bin/perl
# Convert text file of HEX values to Color Computer .BIN file
# Jerry Stratton astoundingscripts.com
# BIN file format from Walter K. Zydhek's Disk BASIC Unravelled II
# Preamble:
# BYTE 0 00 Preamble Flag
# BYTEs 1-2 Length of data block
# Bytes 3-4 Load address
# Postamble:
# Byte 0 FF Postamble flag
# Byte 1-2 0000
# Byte 3-4 EXEC address

while ($option = shift) {
	if (-f $option) {
		$files[$#files+1] = $option;
	} elsif ($option =~ /^[0-9a-f]{4}/i || $option =~ /^[0-9]+$/) {
		print STDERR "Assuming $option is a hexadecimal number\n" if length($option) == 4 && $option =~ /^[0-9]+$/;
		$option = sprintf("%04X", $option) if length($option) != 4 && $option =~ /^[0-9]+$/;

		if (!$loadAddress) {
			$loadAddress = $option;
		} elsif (!$execAddress) {
			$execAddress = $option;
		} else {
			die("Unknown address $option");
		}
	} elsif ($option eq '--basic') {
		$basicCode = 1;
	} elsif ($option eq '--checksum') {
		$verifyChecksum = 1;
	} elsif ($option eq '--columns') {
		die('--columns requires a column count') if $#ARGV < 0 || $ARGV[0] !~ /[1-9][0-9]*/;
		$columnCount = shift;
	} elsif ($option eq '--decimal') {
		$useDecimal = 1;
	} elsif ($option eq '--help') {
		help();
	} elsif ($option eq '--quiet') {
		$quiet = 1;
	} elsif ($option eq '--verbose') {
		$verbose = 1;
	} else {
		die("Unknown option $option");
	}
}

die("Load and Exec addresses required.") if !$loadAddress;
$execAddress = $loadAddress if !$execAddress;

@ARGV = @files;
while (<>) {
	chomp;
	$lineCount = 0 if $ARGV ne $file;
	$file = $ARGV;
	$lineCount++;
	next if /^#/;
	next if /^$/;
	if ($basicCode) {
		next if !/^[1-9][0-9]* DATA/;
		s/^[1-9][0-9]* DATA *//;
	}

	#verify that the *previous* line contained the correct amount of columns
	#this ensures that the final line does not get checked, as it often contains fewer columns
	die($#numbers+1, " columns in line $lineCount ($file) with text: $_") if $#hexes >= 0 && $columnCount && $#hexes != $columnCount-1;

	@numbers = split(/[ ,\t]+/);
	while (@numbers) {
		$number = shift @numbers;
		next if $number =~ /^$/;
		last if ($number eq 'END' or $number eq '999' or $number eq '-1') && $basicCode && $#numbers == -1;

		if (!$useDecimal) {
			die("Invalid hexadecimal: $number in line $lineCount ($file) with text: $_") if $number !~ /^[0-9a-f]{1,2}$/i;
			$number = hexToDecimal($number);
		}
		if ($verifyChecksum && $number > 255) {
			die("Checksum does not match in line $lineCount ($file) with text: $_") if $number != $checksum;
			$checksum = 0;
		} else {
			die("Invalid value: $number in line $lineCount ($file) with text: $_") if $number < 0 || $number > 255;
			$code .= chr($number);
			$checksum += $number;
		}
	}
}

$length = sprintf("%04X", length($code));

if ($verbose) {
	print STDERR "Load address: $loadAddress (${\hex($loadAddress)})\n";
	print STDERR "EXEC address: $execAddress (${\hex($execAddress)})\n";
	print STDERR "Length: $length (${\hex($length)})\n";
}

$length = twoByteDehexer($length);
$load = twoByteDehexer($loadAddress);
$exec = twoByteDehexer($execAddress);

$preamble = chr(0) . $length . $load;
$postamble = chr(255) . chr(0) . chr(0) . $exec;
$code = $preamble . $code . $postamble;

print $code if !$quiet;

sub twoByteDehexer {
	my $hex = shift;

	my $hex1 = dehexByte(substr($hex, 0, 2));
	my $hex2 = dehexByte(substr($hex, 2, 2));

	return "$hex1$hex2";
}

sub dehexByte {
	my $hex = shift;

	$dec = hexToDecimal($hex);

	return chr($dec);
}

sub hexToDecimal {
	my $hex = shift;

	$hex = "0$hex" if length($hex) < 2;

	my $char1 = substr($hex, 0, 1);
	my $char2 = substr($hex, 1, 1);

	$char1 = dehexChar($char1);
	$char2 = dehexChar($char2);

	return $char1*16+$char2;
}

sub dehexChar {
	my $char = shift;

	$char = uc($char);
	if ($char == 0 && $char ne '0') {
		$char = ord($char) - ord('A') + 10;
	}
	return $char;
}

sub help {
	print "$0 <load address> [exec address] [filenames] [--columns <column count>] [--basic] [--help] [--verbose]\n";
	print "\tload address: decimal or hex address for location of binary data in CoCo RAM\n";
	print "\texec address: decimal or hex address for starting execution; defaults to load address\n";
	print "\tfilenames: file[s] to pull data from; data can also be piped\n";
	print "\t--basic: data file is a text BASIC file; pull hex values from DATA lines\n";
	print "\t--checksum: assume that any number greater than 255 is a simple checksum, counting up since the last checksum\n";
	print "\t--columns <column count>: verify that each line contains a specific column count\n";
	print "\t--decimal: the numbers are decimal numbers, not hexadecimal\n";
	print "\t--help: print this text\n";
	print "\t--quiet: do not output bin data\n";
	print "\t--verbose: provide information about the binary program\n";
	exit();
}
