#!perl #------------------------------------------------------------------------------ # $Id: build_zx48_dz80.pl,v 1.1 2010/11/21 22:08:26 Paulo Exp $ # Generate a benchmark diassembly of zx48.rom by dZ80c.exe # - converts hex dump to upper case # - use $HHHH for hex constants # - use decimal base in (ix+DIS) # - do not decode invalid instructions ED.. and DD.. #------------------------------------------------------------------------------ use strict; use warnings; use File::Basename; use File::Slurp; use File::Copy; my @delete_files; END { @delete_files and unlink @delete_files }; my $DIS_CMD = 'dZ80c.exe ! -q -p="<<--" -y="-->>" -d="defb" '; @ARGV==2 or die "Usage: ",basename($0)," INPUT_BINARY OUTPUT_DISASSEMBLY\n"; my($input, $output) = @ARGV; my $output_temp = "$output~"; push @delete_files, $output_temp; open(my $out, ">", $output_temp) or die "write $output: $!\n"; my $in = dis_stream($input); while (my $line = $in->()) { print $out $line; } close $out; move $output_temp, $output; print "Created $output\n"; #------------------------------------------------------------------------------ # return a stream to return disassembled lines sub dis_stream { my($file, $addr, $header, $length) = @_; my $index = qr/\b(?:ix|iy)\b/i; my $hex = qr/[0-9a-f]+/i; my @in; # list of input handles push @in, dis_handle($file, $addr, $header, $length); return sub { for (;;) { return undef unless @in; my $in = $in[-1]; local $_ = <$in>; unless (defined $_) { close($in) or die; pop @in; next; } # convert dump to upper case, ignore lines without dump next unless s/^($hex)(\s+)($hex)/uc($1).$2.uc($3).' '/ige; # reduce space between opcode and args s/^($hex\s+$hex\s+\w+)[ \t]+/$1 /ig; s/ +$//; # convert hex constants to 0xHHHH s/<<--($hex)-->>/'0x'.uc($1)/ige; # convert ix/iy offsets to decimal # s/($index)\+0x($hex)/ hex($2) > 0 ? $1.'+'.hex($2) : $1 /ige; # s/($index)\-0x($hex)/ hex($2) > 0 ? $1.'-'.hex($2) : $1 /ige; s/($index)\+0x00/$1/ig; # (ix+0) -> (ix) # convert DD00/FD00/ED00 created by a single byte file to defb s/^($hex\s+)(DD|FD)00(\s+)nop.*/$1$2$3 defb 0x$2/; s/^($hex\s+ED)00(\s+defb\s+0xED).*; Undocumented 8 T-State NOP.*/$1 $2/; # DD not followed by ix: dump a defb and continue on next address # FD not followed by iy: dump a defb and continue on next address if (/^($hex)\s+DD$hex/ && ! /ix/ || /^($hex)\s+FD$hex/ && ! /iy/ || /^($hex)\s+ED.*; Undocumented 8 T-State NOP/) { my $addr = hex($1); # discard current handle and create new ones 1 while (<$in>); pop @in; push @in, dis_handle($file, $addr+1, $addr+1); push @in, dis_handle($file, $addr, $addr, 1); next; } return $_; } }; } sub dis_handle { my($file, $addr, $header, $length) = @_; $addr ||= 0; $header ||= 0; $length ||= (-s $file) - $header; # make temporary file for this segment my $temp_file = "$file.$addr.$header.$length.bin~"; push @delete_files, $temp_file; my $data = read_file($file, binmode => ':raw'); write_file($temp_file, {binmode => ':raw' }, substr($data, $header, $length)); open(my $in, $DIS_CMD."-m=$addr $temp_file |") or die "dZ80c.exe: $!\n"; return $in; }