aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorH. Peter Anvin <hpa@zytor.com>2014-06-10 20:07:27 -0700
committerH. Peter Anvin <hpa@zytor.com>2014-06-10 20:08:20 -0700
commita86afc2b242239466520211a35b70df770159850 (patch)
tree60194bf3d7dcaba09b5d502a2c8b0056804ce4f8
parent8dce4ce7da12dfc035c1b6e76f536f128709e4ef (diff)
downloadabc80-a86afc2b242239466520211a35b70df770159850.tar.gz
abc80-a86afc2b242239466520211a35b70df770159850.tar.xz
abc80-a86afc2b242239466520211a35b70df770159850.zip
bin2bac: Add program to convert an assembly program to a .BAC file
A fairly compact way to store an assembly program, comparable in overhead to an .ABS file.
-rw-r--r--data/Makefile5
-rw-r--r--data/bacldr.asm45
-rwxr-xr-xdata/bin2bac.pl110
3 files changed, 159 insertions, 1 deletions
diff --git a/data/Makefile b/data/Makefile
index 5f1fba0..2dba4e2 100644
--- a/data/Makefile
+++ b/data/Makefile
@@ -9,7 +9,7 @@ Z80ASM = ../tools/z80asm/z80asm
all : keyboard.mif abc80rom.bin basic80.mif \
mmu.mif chargen.mif videoram.mif fgcol.mif sddrom.mif \
- abcsefi.bas abcdkno.bas abcintl.bas cpm.bas cpm.abs
+ abcsefi.bas abcdkno.bas abcintl.bas cpm.bas cpm.abs cpm.bac
abc80rom.bin : buildrom.pl abcbasic.rom ufddos.bin printer.bin
$(PERL) buildrom.pl $@ 32768 \
@@ -67,6 +67,9 @@ cpm.bas: cpm.bin
cpm.abs: cpm.bin
$(PERL) bin2abs.pl $< 32768 > $@ || ( rm -f $@ ; exit 1 )
+cpm.bac: cpm.bin
+ $(PERL) bin2bac.pl $< 32768 32768 > $@ || ( rm -f $@ ; exit 1 )
+
.bin.bas:
$(PERL) bin2poke.pl $< 16384 100 RETURN | cat charpoke.bah - > $@ \
|| ( rm -f $@ ; exit 1 )
diff --git a/data/bacldr.asm b/data/bacldr.asm
new file mode 100644
index 0000000..2d0e43b
--- /dev/null
+++ b/data/bacldr.asm
@@ -0,0 +1,45 @@
+ org 0 ; This code must be position-independent
+
+BOFA: equ 65052
+offset: equ 0xeeee ; Length of the BASIC prefix
+
+ ld hl,(BOFA)
+ ld de,offset
+ add hl,de
+
+ ld b,0
+
+ ; Get data block length
+loop:
+ ld a,-10
+ add (hl)
+ jr nc,run ; End of file marked by an empty REM
+
+ ; Skip length byte + line number (2 bytes) + string opcode (3 bytes)
+ ld c,6
+ add hl,bc
+
+ ; Load address
+ ld e,(hl)
+ inc hl
+ ld d,(hl)
+ inc hl
+
+ ; Copy remaining data
+ ld c,a
+ ldir
+
+ ; Skip return + CR
+ inc hl
+ inc hl
+ jr loop
+
+run:
+; jp 0x1234 ; Replace with RET if no entry point
+ ret
+ nop
+ nop
+
+endbyte:
+ defb 13
+data:
diff --git a/data/bin2bac.pl b/data/bin2bac.pl
new file mode 100755
index 0000000..4ef7eef
--- /dev/null
+++ b/data/bin2bac.pl
@@ -0,0 +1,110 @@
+#!/usr/bin/perl
+
+use bytes;
+
+sub genpad($$) {
+ my($left, $final) = @_;
+
+ printf STDERR "Padding %d bytes\n", $$left;
+
+ my $o = ($final ? "\x01" : "\x00") . ("\x00" x $$left);
+ $$left = 252; # Bytes left in block minus end marker
+
+ return $o;
+}
+
+sub bacstmt($$$) {
+ my($line, $left, $data) = @_;
+ my $l = length($data) + 4;
+ my $d = pack("Cv", $l, $line) . $data . "\x0d";
+
+ printf STDERR "bacstmt: l = %d, left = %d\n", $l, $$left;
+
+ if ($l > $$left) {
+ $d = genpad($left,0).$d;
+ }
+
+ $$left -= $l;
+ return $pad.$d;
+}
+
+sub makebac($$) {
+ my($segdata, $entrypt) = @_;
+
+ # <bacldr.asm code>
+ my $bld1 = "\x2a\x1c\xfe\x11";
+ # 16-bit offset from BOFA to first data line between $bld1 and $bld2
+ my $bld2 = "\x19\x06\x00\x3e\xf6\x86\x30\x0e\x0e\x06";
+ $bld2 .= "\x09\x5e\x23\x56\x23\x4f\xed\xb0\x23\x23\x18\xed";
+ # C3 = JP, 00C9 is the address for END in all ABC80 BASIC interpreters
+ $bld2 .= pack("Cv", 0xc3, defined($entrypt) ? $entrypt : 0x00c9);
+
+ # +2 for the offset, +2 for the final 0xbb + CR
+ my $bldlen = length($bld1) + length($bld2) + 2 + 2;
+ print STDERR "bldlen = ", $bldlen, "\n";
+
+ my $q = "\x82"; # Output (program start marker)
+ my $left = 251; # Bytes left in block
+ my $r = 0; # Last emitted line number
+
+ # 1 E%=PEEK(65054%)+SWAP%(PEEK(65055%)) [EOFA]
+ $q .= bacstmt(++$r, \$left,
+ "\x83\xc1\xf1\x45\x37\x80\xc7\x1e\xfe\xce\x36\xc7".
+ "\x1f\xfe\xce\x36\xce\x34\xf5\xb7");
+ # 2 Z%=CALL(E%-<loader offset>)
+ $q .= bacstmt(++$r, \$left,
+ "\x83\xc1\xf1\x5a\x37\x80\xc1\xf1\x45\xba\x0d\xd5\xc7".
+ pack("v",$bldlen)."\xf8\xce\x3a\xb7");
+
+ my $pfxlen = length($q) - 1; # The initial 0x82 isn't stored in RAM
+ my $bld = $bld1 . pack("v", $pfxlen) . $bld2;
+ print STDERR "pfxlen = ", $pfxlen, "\n";
+
+ foreach my $sega ( sort(keys(%{$segdata})) ) {
+ my $addr = $sega;
+ my $data = ${$segdata}{$sega};
+
+ my $i = 0;
+ my $dl = length($data);
+ while ($i < $dl) {
+ my $l = $dl - $i;
+
+ printf STDERR "left = %d, l = %d\n", $left, $dl;
+
+ # 9 = 3 byte BAC header + 6 byte overhead + CR
+ $q .= genpad(\$left,0) if ($left <= 10);
+ $l = $left-10 if ($l > $left-10);
+
+ printf STDERR "Line %d address %04X, %d/%X bytes\n",
+ $r+1, $addr, $l, $l;
+
+ # String expression + address + data + return
+ $q .= bacstmt(++$r, \$left, "\xcb\"".pack("C", $l+2).
+ pack("v",$addr).substr($data,$i,$l)."\xbb");
+
+ $i += $l;
+ $addr += $l;
+ }
+ }
+
+ # Terminal END statement
+ $q .= bacstmt(++$r, \$left, "\x86\x8a"); # END
+
+ # Loader code (string expression)
+ $q .= bacstmt(++$r, \$left, "\xcb\"".pack("C",length($bld)).$bld."\xbb");
+ $q .= genpad(\$left,1);
+
+ return $q;
+}
+
+($file, $org, $entry) = @ARGV;
+
+$org = oct $org if ( $org =~ /^0/ );
+
+open(FILE, '<:raw', $file) or die "$0: $file: $!\n";
+$l = read(FILE, $dd, 65536);
+close(FILE);
+
+$segs{$org} = $dd;
+
+print makebac(\%segs, $entry);