diff --git a/software/tools/assemble_rfs.sh b/software/tools/assemble_rfs.sh new file mode 100755 index 0000000..8d33210 --- /dev/null +++ b/software/tools/assemble_rfs.sh @@ -0,0 +1,64 @@ +#!/bin/bash +######################################################################################################### +## +## Name: assemble_roms.sh +## Created: August 2018 +## Author(s): Philip Smart +## Description: Sharp MZ series ROM assembly tool +## This script takes Sharp MZ ROMS in assembler format and compiles/assembles them +## into a ROM file using the GLASS Z80 assembler. +## +## Credits: +## Copyright: (c) 2018 Philip Smart +## +## History: August 2018 - Initial script written. +## +######################################################################################################### +## This source file is free software: you can redistribute it and#or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## This source file is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program. If not, see . +######################################################################################################### + +TOOLDIR=../../tools +JARDIR=../../tools +ASM=glass.jar +BUILDROMLIST="rfs rfs_mrom" +BUILDMZFLIST="" +ASMDIR=../software/asm +INCDIR=../software/asm/include +ROMDIR=../software/roms +MZFDIR=../software/mzf + +# Go through list and build image. +# +for f in ${BUILDROMLIST} ${BUILDMZFLIST} +do + echo "Assembling: $f..." + + # Assemble the source. + echo "java -jar ${JARDIR}/${ASM} ${ASMDIR}/${f}.asm ${ASMDIR}/${f}.obj ${ASMDIR}/${f}.sym" + java -jar ${JARDIR}/${ASM} ${ASMDIR}/${f}.asm ${ASMDIR}/${f}.obj ${ASMDIR}/${f}.sym -I ${INCDIR} + + # On successful compile, perform post actions else go onto next build. + # + if [ $? = 0 ] + then + # The object file is binary, no need to link, copy according to build group. + if [[ ${BUILDROMLIST} = *"${f}"* ]]; then + echo "Copy ${ASMDIR}/${f}.obj to ${ROMDIR}/${f}.rom" + cp ${ASMDIR}/${f}.obj ${ROMDIR}/${f}.rom + else + echo "Copy ${ASMDIR}/${f}.obj to ${MZFDIR}/${f}.mzf" + cp ${ASMDIR}/${f}.obj ${MZFDIR}/${f}.mzf + fi + fi +done diff --git a/software/tools/assemble_roms.sh b/software/tools/assemble_roms.sh new file mode 100755 index 0000000..cf69cde --- /dev/null +++ b/software/tools/assemble_roms.sh @@ -0,0 +1,64 @@ +#!/bin/bash +######################################################################################################### +## +## Name: assemble_roms.sh +## Created: August 2018 +## Author(s): Philip Smart +## Description: Sharp MZ series ROM assembly tool +## This script takes Sharp MZ ROMS in assembler format and compiles/assembles them +## into a ROM file using the GLASS Z80 assembler. +## +## Credits: +## Copyright: (c) 2018 Philip Smart +## +## History: August 2018 - Initial script written. +## +######################################################################################################### +## This source file is free software: you can redistribute it and#or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## This source file is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program. If not, see . +######################################################################################################### + +TOOLDIR=../../tools +JARDIR=../../tools +ASM=glass.jar +BUILDROMLIST="rfs rfs_mrom IPL monitor_SA1510 monitor_80c_SA1510 monitor_mz-1r12 quickdisk_mz-1e05 quickdisk_mz-1e14 monitor_1Z-013A monitor_80c_1Z-013A" +BUILDMZFLIST="hi-ramcheck sharpmz-test" +ASMDIR=../software/asm +INCDIR=../software/asm/include +ROMDIR=../software/roms +MZFDIR=../software/mzf + +# Go through list and build image. +# +for f in ${BUILDROMLIST} ${BUILDMZFLIST} +do + echo "Assembling: $f..." + + # Assemble the source. + echo "java -jar ${JARDIR}/${ASM} ${ASMDIR}/${f}.asm ${ASMDIR}/${f}.obj ${ASMDIR}/${f}.sym" + java -jar ${JARDIR}/${ASM} ${ASMDIR}/${f}.asm ${ASMDIR}/${f}.obj ${ASMDIR}/${f}.sym -I ${INCDIR} + + # On successful compile, perform post actions else go onto next build. + # + if [ $? = 0 ] + then + # The object file is binary, no need to link, copy according to build group. + if [[ ${BUILDROMLIST} = *"${f}"* ]]; then + echo "Copy ${ASMDIR}/${f}.obj to ${ROMDIR}/${f}.rom" + cp ${ASMDIR}/${f}.obj ${ROMDIR}/${f}.rom + else + echo "Copy ${ASMDIR}/${f}.obj to ${MZFDIR}/${f}.mzf" + cp ${ASMDIR}/${f}.obj ${MZFDIR}/${f}.mzf + fi + fi +done diff --git a/software/tools/mzftool.pl b/software/tools/mzftool.pl new file mode 100755 index 0000000..aa70ada --- /dev/null +++ b/software/tools/mzftool.pl @@ -0,0 +1,665 @@ +#! /usr/bin/perl +######################################################################################################### +## +## Name: mzftool.pl +## Created: August 2018 +## Author(s): Philip Smart +## Description: Sharp MZ series MZF (Sharp Tape File) management tool. +## This script identifies the type of MZF file and can add or delete headers as required. +## Useful for seperating MZF compilations into Basic/Pascal/Machine Code etc. +## Also useful to add headers to homegrow machine code programs. +## +## Credits: +## Copyright: (c) 2018 Philip Smart +## +## History: August 2018 - Initial script written. +## +######################################################################################################### +## This source file is free software: you can redistribute it and#or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## This source file is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program. If not, see . +######################################################################################################### + +# Title and Versioning. +# +$TITLE = "MZF Tool"; +$VERSION = "0.1"; +$VERSIONDATE = "25.09.2018"; + +# Global Modules. +# +#use strict +use Getopt::Long; +use IO::File; +use File::stat; +use File::Copy; +use Time::localtime; +use POSIX qw(tmpnam); +use Env qw(KPLUSHOME3 SYBASE SYBASE_OCS DSQUERY); +use sigtrap qw(die normal-signals); + +# Error return codes. +# +$ERR_BADFILENAME = 1; +$ERR_BADFILEDATA = 2; +$ERR_BADFILECREATE = 3; +$ERR_BADFUNCARGS = 4; +$ERR_BADSYSCALL = 5; +$ERR_BADCHECK = 6; +$ERR_BADENV = 7; +$ERR_SYBSERVER = 8; +$ERR_BADARGUMENTS = 9; + +# Run-time constants. +# +$PROGNAME = $0; + +# Run-time globals. Although in Perl you can just specify variables, keeping with most +# high-order languages it is good practise to specify non-local variables in a global header +# which aids visual variable tracking etc. +# +$dbh = 0; # Handle to a Sybase object. +$logh = 0; # Handle to open log file. +$logName = ""; # Temporary name of log file. +$logMode = "terminal"; # Default logging mode for logger. + + +# Configurables!! +# +$SENDMAIL = "/usr/lib/sendmail -t"; +@errorMailRecipients = ( "philip.smart\@net2net.org" ); +$errorMailFrom = "error\@localhost"; +$errorMailSubject = "MZF Tool Errors..."; +$PERL = "perl"; +$PERLFLAGS = ""; + + +################################################################################## +# GENERIC SUB-ROUTINES +################################################################################## + +# Sub-routine to close the log file and email its contents to required participants. +# +sub logClose +{ + # Locals. + local( $idx, $line, @mailRecipients, $mailFrom, $mailSubject, $mailHeader ); + + # No point closing log if one wasnt created!! + # + if($logName eq "" || $sendEmail == 0) + { + return; + } + + # Back to beginning of file, to copy into email. + # + seek($logh, 0, 0); + + # Build up an email to required recipients and send. + # + open(SENDMAIL, "|$SENDMAIL") or die "Cannot open $SENDMAIL: $!"; + for($idx=0; $idx < @errorMailRecipients; $idx++) + { + print SENDMAIL "To: $errorMailRecipients[$idx]\n"; + } + print SENDMAIL "Reply-to: $errorMailFrom\n"; + print SENDMAIL "From: $errorMailFrom\n"; + print SENDMAIL "Subject: $errorMailSubject\n"; + print SENDMAIL "Content-type: text/plain\n\n"; + while( $line = <$logh> ) + { + chomp($line); + print SENDMAIL "$line\n"; + } + close(SENDMAIL); + + # Delete the logfile, not needed. + # + unlink($logName) or die "Couldn't unlink Error File $logName : $!"; +} + +# Function to write a message into a log file. The logfile is a temporary buffer, used +# to store all messages until program end. Upon completion, the buffer is emailed to required +# participants. +# +sub logWrite +{ + # Get parameters, define locals. + local( $mode, $text ) = @_; + local( $date ); + + # Get current date and time for timestamping the log message. + # + $date = `date +'%Y.%m.%d %H:%M:%S'`; + chomp($date); + + # In terminal mode (=interactive mode), always log to STDOUT. + # + if($logMode eq "terminal") + { + if(index($mode, "ND") == -1) + { + print "$date "; + } + print "$text"; + if(index($mode, "NR") == -1) + { + print "\n"; + } + + # Die if required. + # + if (index($mode, 'die') != -1) + { + print "$date Terminating at program request.\n"; + exit 1; + } + return; + } + + # If the logfile hasnt been opened, open it. + # + if($logName eq "") + { + # Try new temporary filenames until we get one that doesnt already exist. + do { + $logName = tmpnam(); + } until $logh = IO::File->new($logName, O_RDWR|O_CREAT|O_EXCL); + + # Automatically flush out log. + $logh->autoflush(1); + + # Only send email if we explicitly die. + # + $sendEmail = 0; + + # Install an atexit-style handler so that when we exit or die, + # we automatically dispatch the log. + END { logClose($logh, $logName); } + } + + # Print to log with date and time stamp. + # + print $logh "$date $text\n"; + + # Print to stdout for user view if in debug mode. + # + if($debugMode > 0) + { + print "$date $text\n"; + } + + # If requested, log termination message and abort program. + # + if (index($mode, 'die') != -1) + { + print $logh "$date Terminating at program request.\n"; + $sendEmail = 1; + exit 1; + } +} + +# Sub-routine to truncate whitespace at the front (left) of a string, returning the +# truncated string. +# +sub cutWhiteSpace +{ + local( $srcString ) = @_; + local( $c, $dstString, $idx ); + $dstString = ""; + + for($idx=0; $idx < length($srcString); $idx++) + { + # If the character is a space or tab, delete. + # + $c = substr($srcString, $idx, 1); + if(length($dstString) == 0) + { + if($c ne " " && $c ne "\t") + { + $dstString = $dstString . $c; + } + } else + { + $dstString = $dstString . $c; + } + } + return($dstString); +} + +# Perl trim function to remove whitespace from the start and end of the string +# +sub trim($) +{ + my $string = shift; + $string =~ s/^\s+//; + $string =~ s/\s+$//; + return $string; +} + +# Left trim function to remove leading whitespace +# +sub ltrim($) +{ + my $string = shift; + $string =~ s/^\s+//; + return $string; +} + +# Right trim function to remove trailing whitespace +# +sub rtrim($) +{ + my $string = shift; + $string =~ s/\s+$//; + return $string; +} + +# Sub-routine to test if a string is empty, and if so, replace +# with an alternative string. The case of the returned string +# can be adjusted according to the $convertCase parameter. +# +sub trString +{ + local( $tstString, $replaceString, $convertCase ) = @_; + local( $dstString ); + + $tstString=cutWhitespace($tstString); + $replaceString=cutWhitespace($replaceString); + if($tstString eq "") + { + $dstString = $replaceString; + } else + { + $dstString = $tstString; + } + + # Convert to Lower Case? + # + if($convertCase == 1) + { + $dstString =~ lc($dstString); + } + # Convert to Upper Case? + # + elsif($convertCase == 2) + { + $dstString =~ uc($dstString); + } + return($dstString); +} + +# Sub-routine to test if a numeric is empty, and if so, set to a +# given value. +# +sub trNumeric +{ + local( $tstNumber, $replaceNumber ) = @_; + local( $dstNumber ); + + if(!defined($tstNumber) || $tstNumber eq "" || cutWhitespace($tstNumber) eq "") + { + $dstNumber = $replaceNumber; + } else + { + $dstNumber = $tstNumber; + } + + return($dstNumber); +} + +# Function to look at a string and decide wether its contents +# indicate Yes or No. If the subroutine cannot determine a Yes, +# then it defaults to No. +# +sub yesNo +{ + local( $srcString ) = @_; + local( $dstString, $yesNo ); + $yesNo = "N"; + + $dstString=lc(cutWhiteSpace($srcString)); + if($dstString eq "y" || $dstString eq "yes" || $dstString eq "ye") + { + $yesNo = "Y"; + } + return( $yesNo ); +} + +# Sub-routine to encrypt an input string, typically a password, +# using the Collateral Management Encrypt utility. +# +sub encrypt +{ + local( $srcPasswd ) = @_; + local( $encPasswd ); + $encPasswd=""; + + # Call external function to perform the encryption. + # + if($srcPasswd ne "") + { + $encPasswd=`$PROG_ENCRYPT -p $srcPasswd 2>&1`; + chomp($encPasswd); + } + return($encPasswd); +} + +# Sub-routine to test if a string is empty, and if so, replace +# with an alternative string. The case of the returned string +# can be adjusted according to the $convertCase parameter. +# +sub testAndReplace +{ + local( $tstString, $replaceString, $convertCase ) = @_; + local( $dstString ); +#printf("Input:$tstString,$replaceString\n"); + $tstString=cutWhiteSpace($tstString); + $replaceString=cutWhiteSpace($replaceString); + if($tstString eq "") + { + $dstString = $replaceString; + } else + { + $dstString = $tstString; + } + + # Convert to Lower Case? + # + if($convertCase == 1) + { + $dstString =~ lc($dstString); + } + # Convert to Upper Case? + # + elsif($convertCase == 2) + { + $dstString =~ uc($dstString); + } +#printf("Output:$dstString:\n"); + return($dstString); +} + +# Subroutine to generate a unique name by adding 2 digits onto the end of it. A hash of existing +# names is given to compare the new value against. +# +sub getUniqueName +{ + local( $cnt, $uniqueName ) = ( 0, "" ); + local( $startName, $maxLen, $usedNames ) = @_; + + # Go through looping, adding a unique number onto the end of the string, then looking it + # up to see if it already exists. + # + $uniqueName = substr($startName, 0, $maxLen); + while(defined($$usedNames{$uniqueName})) + { + $uniqueName = substr($uniqueName, 0, $maxLen-2) . sprintf("%02d", $cnt); + $cnt++; + if($cnt > 99) + { + logWrite("die", "Unique identifier > 99: $uniqueName"); + } + } + + # Return unique name. + # + return($uniqueName); +} + +# Sub-routine to process command line arguments. New style POSIX argument format used. +# +sub argOptions +{ + local ( $writeUsage, $msg, $exitCode ) = @_; + + if( $writeUsage == 1 ) + { + print STDOUT "Usage: $PROGNAME [] \n"; + print STDOUT " commands= --help |\n"; + print STDOUT " --verbose |\n"; + print STDOUT " --command= |\n"; + print STDOUT " --mzffile= {IDENT|ADDHEADER|DELHEADER} |\n"; + print STDOUT " --srcfile= {ADDHEADER} |\n"; + print STDOUT " --dstfile= {DELHEADER} |\n"; + print STDOUT " --filename= (ADDHEADER} |\n"; + print STDOUT " --loadaddr= (ADDHEADER} |\n"; + print STDOUT " --execaddr= (ADDHEADER} |\n"; + print STDOUT " --tapetype=<1 byte type value> (ADDHEADER} |\n"; + print STDOUT " --comment= (ADDHEADER} |\n"; + print STDOUT " options = --debug=<1=ON, 0=OFF>\n"; + print STDOUT "\n"; + } + if($msg ne "") + { + print STDOUT "Error: $msg\n"; + } + exit( $exitCode ); +} + + +################################################################################## +# END OF GENERIC SUB-ROUTINES +################################################################################## + + +################################################################################## +# +# MAIN PROGRAM +# +################################################################################## + +# Locals. +# +local( $time, $date, $mzfExists, $a_mromExists, $b_mromExists, $k_mromExists, $m7_mromExists, $m8_mromExists, $m12_mromExists, $m20_mromExists, + $a_80c_mromExists, $b_80c_mromExists, $k_80c_mromExists, $m7_80c_mromExists, $m8_80c_mromExists, $m12_80c_mromExists, $m20_80c_mromExists, + $mzf_type, $mzf_filename, $mzf_size, $mzf_loadaddr, $mzf_execaddr, $mzf_comment); + +# Get current time and date. +# +$time = `date +'%H:%M:%S'`; +$date = `date +'%d.%m.%Y'`; +chomp($time); +chomp($date); + +# Sign-on. +# +print STDOUT "$TITLE (v$VERSION) \@ ${VERSIONDATE}\n\n"; + +# Parse arguments and put into required variables. +# +$verbose = 0; +$fileName = ""; +$s_loadAddr = ""; +$s_execAddr = ""; +$s_tapeType = ""; +$comment = ""; +GetOptions( "debug=n" => \$debugMode, # Debug Mode? + "verbose" => \$verbose, # Show details? + "mzffile=s" => \$mzfFile, # MZF file. + "dstfile=s" => \$dstFile, # Destination file (for header removal). + "srcfile=s" => \$srcFile, # Source file (for header adding). + "filename=s" => \$fileName, # Filename to insert into header. + "loadaddr=s" => \$s_loadAddr, # Tape load address. + "execaddr=s" => \$s_execAddr, # Tape execution address. + "tapetype=s" => \$s_tapeType, # Tape type (ie. 01 = Machine Code). + "comment=s" => \$comment, # Tape comment string. + "command=s" => \$command, # Command to execute. + "help" => \$help, # Help required on commands/options? + ); + +# Help required? +# +if(defined($help)) +{ + argOptions(1, ""); +} + +# Convert number arguments from string to decimal. +# +if($s_loadAddr ne "") +{ + $loadAddr = oct($s_loadAddr); +} +if($s_execAddr ne "") +{ + $execAddr = oct($s_execAddr); +} +if($s_tapeType ne "") +{ + $tapeType = oct($s_tapeType); +} + +# Verify command. +# +if($command eq "IDENT" || $command eq "ADDHEADER" || $command eq "DELHEADER") +{ + 1; +} +else +{ + argOptions(1, "Illegal command given on command line:$command.\n",$ERR_BADARGUMENTS); +} + +# Check that the additional parameters have been provided for the ADDHEADER command. +if($command eq "ADDHEADER" && ($fileName eq "" || !defined($loadAddr) || !defined($execAddr) || !defined($tapeType)) ) +{ + argOptions(3, "ADDHEADER command requires the following parameters to be provided: --filename, --loadaddr, --execaddr, --tapetype\n",$ERR_BADARGUMENTS); +} + +# For ident or delete header commands, we need to open and read the mzf file. +# +if(($command eq "IDENT" || $command eq "DELHEADER") && defined($mzfFile) && $mzfFile ne "") +{ + # If defined, can we open it? + # + if( ! open(MZFFILE, "<".$mzfFile) ) + { + argOptions(1, "Cannot open MZF file: $mzfFile.\n",$ERR_BADFILENAME); + } + + @MZF = (); + binmode(MZFFILE); + local $/ = \1; + $cnt = 0; + $skip = 0; + while ( my $byte = ) + { + $MZF[$cnt] = $byte; + $cnt++; + } + $MZF_SIZE = $cnt; + + # Once the MZF is in memory, analyse the details and output. + # + $mzf_header = pack('a'x128, @MZF); + ($mzf_type, $mzf_filename, $mzf_size, $mzf_loadaddr, $mzf_execaddr, $mzf_comment) = unpack 'c1 a17 v4 v4 v4 a104', $mzf_header; + $mzf_filename =~ s/\r|\n//g; + + # Output detail if requested. + # + if($verbose) + { + printf STDOUT "File Name : %s\n", $mzf_filename; + printf STDOUT "File Type : %02x\n", $mzf_type; + printf STDOUT "File Size : %04x\n", $mzf_size; + printf STDOUT "File Load Address : %04x\n", $mzf_loadaddr; + printf STDOUT "File Exec Address : %04x\n", $mzf_execaddr; + printf STDOUT "Comment : %s\n", $mzf_comment; + } + + # For the DELHEADER command, a destination needs to be provided and opened. + if($command eq "DELHEADER" && defined($dstFile) && $dstFile ne "") + { + if( ! open(DSTFILE, ">".$dstFile) ) + { + argOptions(1, "Cannot open the destination file: $dstFile.\n",$ERR_BADFILENAME); + } + } +} +elsif($command eq "ADDHEADER" && defined($mzfFile) && $mzfFile ne "") +{ + # If defined, can we create it? + # + if( ! open(MZFFILE, ">".$mzfFile) ) + { + argOptions(1, "Cannot create MZF file: $mzfFile.\n",$ERR_BADFILENAME); + } + + # For this command, a source file needs to exist and opened. + if(defined($srcFile) && $srcFile ne "") + { + if( ! open(SRCFILE, "<".$srcFile) ) + { + argOptions(1, "Cannot open the source file: $srcFile.\n",$ERR_BADFILENAME); + } + + @SRC = (); + binmode(SRCFILE); + local $/ = \1; + $cnt = 0; + $skip = 0; + while ( my $byte = ) + { + $SRC[$cnt] = $byte; + $cnt++; + } + $SRC_SIZE = $cnt; + } +} +else +{ + argOptions(2, "No MZF file given, use --mzffile=.\n"); +} + +# Process command as necessary. +# +if($command eq "ADDHEADER") +{ + # Build the header based on given information and size of src file. + $mzf_size = scalar @SRC; + $mzf_type = $tapeType; # For exit code. + $mzf_header = pack('c1 a17 v v v', $tapeType, $fileName, $mzf_size, $loadAddr, $execAddr); + $mzf_header .= pack('a104', $comment) ; + + # Store in file. + print MZFFILE $mzf_header; + + # Now add the source data. + foreach my $byte (@SRC) { print MZFFILE $byte; } + + # All done. + close MZFFILE; + + # Output detail if requested. + # + if($verbose) + { + printf STDOUT "File Name : %s\n", $fileName; + printf STDOUT "File Type : %02x\n", $tapeType; + printf STDOUT "File Size : %04x\n", $mzf_size; + printf STDOUT "File Load Address : %04x\n", $loadAddr; + printf STDOUT "File Exec Address : %04x\n", $execAddr; + printf STDOUT "Comment : %s\n", $comment; + } +} +# For delete, simply write out the tape contents less the header (first 128 bytes). +elsif($command eq "DELHEADER") +{ + my $cnt = 0; + foreach my $byte (@MZF) { if($cnt++ >= 128) { print DSTFILE $byte; } } + close DSTFILE; +} + +# Exit code is the type of MZF file. +exit $mzf_type;