#!/usr/bin/perl -w
#
# By Pierre Sarrazin
# This file is in the public domain.
use strict;
use Getopt::Long;
use File::Basename;
my $program = basename($0);
my $version = "0.0.1";
my $numErrors = 0;
my $numWarnings = 0;
my $dryRun = 0;
my $verbose = 0;
my $dumpContents = 0;
my $dumpToRawFiles = 0;
my $dumpToBinFiles = 0;
sub usage
{
my ($exitStatus) = @_;
print <<__EOF__;
$program $version
Copyright (C) 2013 Pierre Sarrazin
Usage: $program [OPTIONS] BINFILE
Dumps the list of blocks comprising the named CoCo binary file.
Options:
--help Print this help page.
--version Print this program's name and version number.
--not-really Only show what would happen, do not do it for real.
--verbose Print more details of what is happening.
__EOF__
exit($exitStatus);
}
sub errmsg
{
print "$program: ERROR: ";
printf @_;
print "\n";
++$numErrors;
}
sub warnmsg
{
print "$program: Warning: ";
printf @_;
print "\n";
++$numWarnings;
}
my $numContiguousBlocks = 0;
my $firstBlockStart = 0;
sub processBlock($)
{
my ($rhBlock) = @_;
printf("Contiguous block at \$%04X of length \$%04X\n",
$rhBlock->{start},
length($rhBlock->{content})) if 0;
++$numContiguousBlocks;
if ($numContiguousBlocks == 1)
{
$firstBlockStart = $rhBlock->{start};
}
if ($dumpContents)
{
my $fh;
if (!open($fh, "| od -tx1c"))
{
errormsg("failed to open pipe to od command: $!\n");
return;
}
if (syswrite($fh, $rhBlock->{content}) != length($rhBlock->{content}))
{
errormsg("failed to write to pipe to od command: $!\n");
return;
}
if (!close($fh))
{
errormsg("failed to close pipe to od command: $!\n");
return;
}
}
if ($dumpToRawFiles || $dumpToBinFiles)
{
my $fn = sprintf("coco-bin-block-%04x.%s",
$rhBlock->{start},
$dumpToRawFiles ? "raw" : "bin");
print "Creating block file $fn with ", length($rhBlock->{content}), " bytes\n";
my $fh;
if (!open($fh, "> $fn"))
{
errormsg("failed to create file for block: $!\n");
return;
}
if ($dumpToBinFiles)
{
my $header = pack("Cnn", length($rhBlock->{content}), $rhBlock->{start});
if (syswrite($fh, $header) != 5)
{
errormsg("failed to write header to block file: $!\n");
return;
}
}
if (syswrite($fh, $rhBlock->{content}) != length($rhBlock->{content}))
{
errormsg("failed to write to block file: $!\n");
return;
}
if ($dumpToBinFiles)
{
my $footer = pack("Cnn", 0, 0);
if (syswrite($fh, $footer) != 5)
{
errormsg("failed to write footer to block file: $!\n");
return;
}
}
if (!close($fh))
{
errormsg("failed to close block file: $!\n");
return;
}
}
}
###############################################################################
my $showUsage = 0;
if (!GetOptions(
"help" => \$showUsage,
"version" => \$showUsage,
"not-really" => \$dryRun,
"verbose" => \$verbose,
"dump-hex" => \$dumpContents,
"dump-to-raw-files" => \$dumpToRawFiles,
"dump-to-bin-files" => \$dumpToBinFiles,
))
{
exit 1;
}
usage(0) if $showUsage;
usage(1) if @ARGV != 1;
$| = 1; # no buffering on STDOUT
my $binFilename = shift;
if (!open(BIN, $binFilename))
{
errmsg("failed to open $binFilename: $!");
exit 1;
}
my $osAt2600 = 0;
my $contentLength = 0;
my $entryPoint;
my $rhBlock;
# content => undef, # string
# start => undef, # adress of beginning of block
while ((read(BIN, $_, 5) || 0) == 5)
{
my ($code, $n0, $n1) = unpack("Cnn", $_);
unless ($code == 0 || $code == 0xFF)
{
errmsg("invalid byte \$%02x at offset %u", $code, tell(BIN) - length($_));
exit 1;
}
if ($code == 0xFF)
{
$entryPoint = $n1;
last;
}
printf("Block at \$%04X (%5u) of length \$%04X (%5u): end at \$%04X\n",
$n1, $n1, $n0, $n0, $n1 + $n0);
$contentLength += $n0;
read(BIN, my $data, $n0) == $n0 or die;
if ($n1 == 0x2600 && substr($data, 0, 2) eq "OS")
{
$osAt2600 = 1;
}
if (defined $rhBlock && $rhBlock->{start} + length($rhBlock->{content}) == $n1)
{
$rhBlock->{content} .= $data;
next;
}
if (defined $rhBlock) # previous block not continued
{
processBlock($rhBlock);
$rhBlock = undef;
}
if (!defined $rhBlock)
{
$rhBlock = { content => $data, start => $n1 };
}
}
close(BIN) or die;
if (defined $rhBlock)
{
processBlock($rhBlock);
$rhBlock = undef;
}
if (!defined $entryPoint)
{
print "ERROR: entry point not found.\n";
exit 1;
}
my $fileLen = -s $binFilename;
printf("BIN file length: \$%04X (%5u)\n", $fileLen, $fileLen);
printf("Entry point : \$%04X (%5u)\n", $entryPoint, $entryPoint);
printf("Content length : \$%04X (%5u)\n", $contentLength, $contentLength);
printf("Overhead : %5.1f %%\n", 100 * ($fileLen - $contentLength) / $contentLength);
if ($numContiguousBlocks == 1)
{
printf("Single continuous block goes from \$%04X (%5u) to \$%04X (%5u)\n",
$firstBlockStart,
$firstBlockStart,
$firstBlockStart + $contentLength,
$firstBlockStart + $contentLength);
}
if ($osAt2600 && $entryPoint == 0x2602)
{
print "Track 34 format.\n";
}
exit 0;