Skip to content

Commit

Permalink
Add support to bin2tap.py for writing PZX files
Browse files Browse the repository at this point in the history
  • Loading branch information
skoolkid committed Jun 28, 2024
1 parent 779612c commit fec445c
Show file tree
Hide file tree
Showing 11 changed files with 148 additions and 58 deletions.
3 changes: 3 additions & 0 deletions skoolkit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@ def get_word(data, index):
def get_word3(data, index):
return get_word(data, index) + 65536 * data[index + 2]

def as_dword(num):
return (num % 256, (num >> 8) % 256, (num >> 16) % 256, (num >> 24) % 256)

def get_dword(data, index):
return get_word3(data, index) + 16777216 * data[index + 3]

Expand Down
34 changes: 19 additions & 15 deletions skoolkit/bin2tap.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

from skoolkit import SkoolKitError, integer, parse_int, read_bin_file, VERSION
from skoolkit.components import get_snapshot_reader
from skoolkit.tape import write_tap
from skoolkit.tape import write_pzx, write_tap

def _get_str(chars):
return [ord(c) for c in chars]
Expand Down Expand Up @@ -142,9 +142,9 @@ def _get_bank_loader(title, address, start_addr, banks, out7ffd):
data.append(0x80 | out7ffd) # End marker
return (_get_header(title, len(data), address), _make_block(data))

def run(ram, clear, org, start, stack, tapfile, scr, banks, out7ffd, loader_addr):
title = os.path.basename(tapfile)
if title.lower().endswith('.tap'):
def run(ram, clear, org, start, stack, tape_file, scr, banks, out7ffd, loader_addr):
title = os.path.basename(tape_file)
if title.lower().endswith(('.tap', '.pzx')):
title = title[:-4]
if banks is None:
blocks = _get_basic_loader(title, clear, start, scr)
Expand Down Expand Up @@ -178,22 +178,26 @@ def run(ram, clear, org, start, stack, tapfile, scr, banks, out7ffd, loader_addr
for b in sorted(banks):
blocks.append(_make_block(banks[b]))

write_tap(tapfile, blocks)
if tape_file.lower().endswith('.pzx'):
write_pzx(tape_file, blocks)
else:
write_tap(tape_file, blocks)

def main(args):
parser = argparse.ArgumentParser(
usage='bin2tap.py [options] FILE [file.tap]',
description="Convert a binary (raw memory) file or a SNA, SZX or Z80 snapshot into a TAP file. "
"FILE may be a regular file, or '-' to read a binary file from standard input.",
usage='bin2tap.py [options] FILE [OUTFILE]',
description="Convert a binary (raw memory) file or a SNA, SZX or Z80 snapshot into a PZX or TAP file. "
"FILE may be a regular file, or '-' to read a binary file from standard input. "
"If OUTFILE is not given, a TAP file is created.",
add_help=False
)
parser.add_argument('infile', help=argparse.SUPPRESS, nargs='?')
parser.add_argument('outfile', help=argparse.SUPPRESS, nargs='?')
group = parser.add_argument_group('Options')
group.add_argument('--7ffd', metavar='N', dest='out7ffd', type=integer,
help="Add 128K RAM banks to the TAP file and write N to port 0x7ffd after they've loaded.")
help="Add 128K RAM banks to the tape file and write N to port 0x7ffd after they've loaded.")
group.add_argument('--banks', metavar='N[,N...]',
help="Add only these 128K RAM banks to the TAP file (default: 0,1,3,4,6,7).")
help="Add only these 128K RAM banks to the tape file (default: 0,1,3,4,6,7).")
group.add_argument('-b', '--begin', dest='begin', metavar='BEGIN', type=integer,
help="Begin conversion at this address (default: ORG for a binary file, 16384 for a snapshot).")
group.add_argument('-c', '--clear', dest='clear', metavar='N', type=integer,
Expand All @@ -209,7 +213,7 @@ def main(args):
group.add_argument('-s', '--start', dest='start', metavar='START', type=integer,
help="Set the start address to JP to (default: BEGIN).")
group.add_argument('-S', '--screen', dest='screen', metavar='FILE',
help="Add a loading screen to the TAP file. FILE may be a snapshot or a 6912-byte SCR file.")
help="Add a loading screen to the tape file. FILE may be a snapshot or a 6912-byte SCR file.")
group.add_argument('-V', '--version', action='version', version='SkoolKit {}'.format(VERSION),
help='Show SkoolKit version number and exit.')

Expand Down Expand Up @@ -258,19 +262,19 @@ def main(args):
del banks[b]
start = namespace.start or begin
stack = namespace.stack or begin
tapfile = namespace.outfile
if tapfile is None:
tape_file = namespace.outfile
if tape_file is None:
if infile.lower().endswith(('.bin', '.sna', '.szx', '.z80')):
prefix = os.path.basename(infile)[:-4]
elif infile == '-':
prefix = 'program'
else:
prefix = os.path.basename(infile)
tapfile = prefix + ".tap"
tape_file = prefix + ".tap"
scr = namespace.screen
if scr is not None:
if snapshot_reader.can_read(scr):
scr = snapshot_reader.get_snapshot(scr)[16384:23296]
else:
scr = read_bin_file(scr, 6912)
run(ram, clear, begin, start, stack, tapfile, scr, banks, out7ffd, loader_addr)
run(ram, clear, begin, start, stack, tape_file, scr, banks, out7ffd, loader_addr)
7 changes: 2 additions & 5 deletions skoolkit/rzxplay.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
except ImportError: # pragma: no cover
pygame = None

from skoolkit import (VERSION, SkoolKitError, CSimulator, get_dword, get_word,
parse_int, read_bin_file, warn, write)
from skoolkit import (VERSION, SkoolKitError, CSimulator, as_dword, get_dword,
get_word, parse_int, read_bin_file, warn, write)
from skoolkit.pagingtracer import PagingTracer
from skoolkit.simulator import Simulator
from skoolkit.simutils import from_snapshot, get_state
Expand Down Expand Up @@ -130,9 +130,6 @@ def __init__(self, screen=None, p_rectangles=None, c_rectangles=None, clock=None
self.frame_count = 0
self.stop = False

def as_dword(num):
return (num % 256, (num >> 8) % 256, (num >> 16) % 256, (num >> 24) % 256)

def write_rzx(fname, context, rzx_blocks):
creator_b = (83, 107, 111, 111, 108, 75, 105, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
major, minor = re.match('([0-9]+).([0-9]+)', VERSION).groups()
Expand Down
26 changes: 25 additions & 1 deletion skoolkit/tape.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License along with
# SkoolKit. If not, see <http://www.gnu.org/licenses/>.

from skoolkit import SkoolKitError, get_word, get_word3, get_dword, read_bin_file
from skoolkit import SkoolKitError, as_dword, get_word, get_word3, get_dword, read_bin_file
from skoolkit.basic import get_char

ARCHIVE_INFO = {
Expand Down Expand Up @@ -757,3 +757,27 @@ def write_tap(fname, blocks):
length = len(data)
f.write(bytes((length % 256, length // 256)))
f.write(bytes(data))

def write_pzx(fname, blocks):
with open(fname, 'wb') as f:
f.write(b'PZXT\x02\x00\x00\x00\x01\x00')
for i, data in enumerate(blocks):
if i:
f.write(b'PAUS\x04\x00\x00\x00\xe0\x67\x35\x00')
if data[0]:
f.write(b'PULS\x08\x00\x00\x00\x97\x8c\x78\x08\x9b\x02\xdf\x02')
else:
f.write(b'PULS\x08\x00\x00\x00\x7f\x9f\x78\x08\x9b\x02\xdf\x02')
length = len(data)
bits = 0x80000000 + length * 8
f.write(bytes((
68, 65, 84, 65, # DATA
*as_dword(length + 16), # Block length
*as_dword(bits), # Polarity and bit count
177, 3, # Tail pulse (945)
2, # p0
2, # p1
87, 3, 87, 3, # s0 (855, 855)
174, 6, 174, 6, # s1 (1710, 1710)
*data # Data
)))
4 changes: 3 additions & 1 deletion sphinx/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ Changelog

9.3b1
-----
* Added support to :ref:`tapinfo.py` and :ref:`tap2sna.py` for PZX files
* Added support to :ref:`tapinfo.py` and :ref:`tap2sna.py` for reading PZX
files
* Added support to :ref:`bin2tap.py` for writing PZX files
* Added support to :ref:`tap2sna.py <tap2sna-conf>` for the ``m`` (memory)
replacement field in the ``TraceLine`` configuration parameter
* Added support to :ref:`trace.py <trace-conf>` for the ``m`` (memory)
Expand Down
24 changes: 13 additions & 11 deletions sphinx/source/commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ Run `bin2sna.py` with no arguments to see the list of available options::
bin2tap.py
----------
`bin2tap.py` converts a binary (raw memory) file or a SNA, SZX or Z80 snapshot
into a TAP file. For example::
into a PZX or TAP file. For example::

$ bin2tap.py game.bin

Expand All @@ -97,16 +97,16 @@ of code to run) and the stack pointer are set to 65536 minus the length of
`game.bin`. These values can be changed by passing options to `bin2tap.py`. Run
it with no arguments to see the list of available options::

usage: bin2tap.py [options] FILE [file.tap]
usage: bin2tap.py [options] FILE [OUTFILE]

Convert a binary (raw memory) file or a SNA, SZX or Z80 snapshot into a TAP
file. FILE may be a regular file, or '-' to read a binary file from standard
input.
Convert a binary (raw memory) file or a SNA, SZX or Z80 snapshot into a PZX or
TAP file. FILE may be a regular file, or '-' to read a binary file from
standard input. If OUTFILE is not given, a TAP file is created.

Options:
--7ffd N Add 128K RAM banks to the TAP file and write N to port
0x7ffd after they've loaded.
--banks N[,N...] Add only these 128K RAM banks to the TAP file
--7ffd N Add 128K RAM banks to the tape file and write N to
port 0x7ffd after they've loaded.
--banks N[,N...] Add only these 128K RAM banks to the tape file
(default: 0,1,3,4,6,7).
-b BEGIN, --begin BEGIN
Begin conversion at this address (default: ORG for a
Expand All @@ -123,7 +123,7 @@ it with no arguments to see the list of available options::
-s START, --start START
Set the start address to JP to (default: BEGIN).
-S FILE, --screen FILE
Add a loading screen to the TAP file. FILE may be a
Add a loading screen to the tape file. FILE may be a
snapshot or a 6912-byte SCR file.
-V, --version Show SkoolKit version number and exit.

Expand All @@ -145,7 +145,7 @@ crashing. The lowest usable address with the ``--clear`` option on a bare 48K
Spectrum is 23972 (5DA4) if a loading screen is used, or 23952 (0x5D90)
otherwise.

To create a TAP file that loads a 128K game, use the ``--7ffd``, ``--begin``
To create a tape file that loads a 128K game, use the ``--7ffd``, ``--begin``
and ``--clear`` options along with a 128K snapshot or a 128K binary file as
input, where:

Expand All @@ -161,7 +161,7 @@ the number of RAM banks to load) is placed one above the CLEAR address. Use the
address with the ``--clear`` option on a bare 128K Spectrum is 23977 (0x5DA9)
if a loading screen is used, or 23957 (0x5D95) otherwise.

By default, 128K RAM banks 0, 1, 3, 4, 6 and 7 are added to the TAP file. If
By default, 128K RAM banks 0, 1, 3, 4, 6 and 7 are added to the tape file. If
one or more of these RAM banks are not required, use the ``--banks`` option to
specify a smaller set of RAM banks to add. If none of these RAM banks are
required, use ``,`` (a single comma) as the argument to the ``--banks`` option.
Expand All @@ -172,6 +172,8 @@ block on the tape.
+---------+-------------------------------------------------------------------+
| Version | Changes |
+=========+===================================================================+
| 9.3 | Added support for writing PZX files |
+---------+-------------------------------------------------------------------+
| 9.1 | Added the ``--7ffd``, ``--banks`` and ``--loader`` options and |
| | support for writing 128K TAP files |
+---------+-------------------------------------------------------------------+
Expand Down
2 changes: 1 addition & 1 deletion sphinx/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@
('man/bin2sna.py', 'bin2sna.py',
'convert a binary file into an SZX or Z80 snapshot', _authors, 1),
('man/bin2tap.py', 'bin2tap.py',
'convert a binary file or snapshot into a TAP file', _authors, 1),
'convert a binary file or snapshot into a PZX or TAP file', _authors, 1),
('man/rzxinfo.py', 'rzxinfo.py',
'show the blocks in or extract the snapshots from an RZX file', _authors, 1),
('man/rzxplay.py', 'rzxplay.py',
Expand Down
25 changes: 13 additions & 12 deletions sphinx/source/man/bin2tap.py.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,23 @@ bin2tap.py

SYNOPSIS
========
``bin2tap.py`` [options] FILE [file.tap]
``bin2tap.py`` [options] FILE [OUTFILE]

DESCRIPTION
===========
``bin2tap.py`` converts a binary (raw memory) file or a SNA, SZX or Z80
snapshot into a TAP file. FILE may be a regular file, or '-' to read a binary
file from standard input.
snapshot into a PZX or TAP file. FILE may be a regular file, or '-' to read a
binary file from standard input. If OUTFILE is not given, a TAP file is
created.

OPTIONS
=======
--7ffd `N`
Add 128K RAM banks to the TAP file and write `N` to port 0x7ffd after they've
loaded.
Add 128K RAM banks to the tape file and write `N` to port 0x7ffd after
they've loaded.

--banks `N[,N...]`
Add only these 128K RAM banks to the TAP file (default: 0,1,3,4,6,7).
Add only these 128K RAM banks to the tape file (default: 0,1,3,4,6,7).

-b, --begin `BEGIN`
Begin conversion at this address. The default begin address is the origin
Expand Down Expand Up @@ -55,8 +56,8 @@ OPTIONS
must be a decimal number, or a hexadecimal number prefixed by '0x'.

-S, --screen `FILE`
Add a loading screen to the TAP file. `FILE` may be a snapshot or a 6912-byte
SCR file.
Add a loading screen to the tape file. `FILE` may be a snapshot or a
6912-byte SCR file.

-V, --version
Show the SkoolKit version number and exit.
Expand All @@ -82,7 +83,7 @@ Spectrum is 23952 (0x5D90).

128K TAPES
==========
To create a TAP file that loads a 128K game, use the ``--7ffd``, ``--begin``
To create a tape file that loads a 128K game, use the ``--7ffd``, ``--begin``
and ``--clear`` options along with a 128K snapshot or a 128K binary file as
input, where:

Expand All @@ -97,7 +98,7 @@ the number of RAM banks to load) is placed one above the CLEAR address. Use the
``--loader`` option to place it at an alternative address. The lowest usable
address with the ``--clear`` option on a bare 128K Spectrum is 23977 (0x5DA9).

By default, 128K RAM banks 0, 1, 3, 4, 6 and 7 are added to the TAP file. If
By default, 128K RAM banks 0, 1, 3, 4, 6 and 7 are added to the tape file. If
one or more of these RAM banks are not required, use the ``--banks`` option to
specify a smaller set of RAM banks to add. If none of these RAM banks are
required, use ``,`` (a single comma) as the argument to the ``--banks`` option.
Expand All @@ -112,8 +113,8 @@ EXAMPLES
|
| ``bin2tap.py game.bin``
2. Convert ``game.bin`` into a TAP file that starts execution at 32768 when
2. Convert ``game.bin`` into a PZX file that starts execution at 32768 when
loaded:

|
| ``bin2tap.py -s 32768 game.bin``
| ``bin2tap.py -s 32768 game.bin game.pzx``
4 changes: 2 additions & 2 deletions sphinx/source/whatis.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ With SkoolKit you can:
list the BASIC program it contains
* use :ref:`rzxinfo.py` to analyse the blocks in an RZX file, and extract
snapshots from it
* use :ref:`bin2tap.py` to convert a snapshot or raw memory file into a TAP
file
* use :ref:`bin2tap.py` to convert a snapshot or raw memory file into a PZX or
TAP file
* use :ref:`bin2sna.py` to convert a raw memory file into a Z80 or SZX snapshot
* use :ref:`snapmod.py` to modify the register values or memory contents in a
Z80 or SZX snapshot
Expand Down
24 changes: 16 additions & 8 deletions tests/test_bin2tap.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,20 @@ def mock_run(*args):
global run_args
run_args = args

def mock_write_tap(fname, blocks):
def mock_write_tape(fname, blocks):
global tape_fname, tape_blocks
tape_fname = fname
tape_blocks = blocks

class Bin2TapTest(SkoolKitTestCase):
@patch.object(bin2tap, 'write_tap', mock_write_tap)
def _run(self, args, tapfile=None):
if tapfile is None:
tapfile = args.split()[-1][:-4] + '.tap'
@patch.object(bin2tap, 'write_tap', mock_write_tape)
def _run(self, args, tape_file=None):
if tape_file is None:
tape_file = args.split()[-1][:-4] + '.tap'
output, error = self.run_bin2tap(args)
self.assertEqual(output, '')
self.assertEqual(error, '')
self.assertEqual(tape_fname, tapfile)
self.assertEqual(tape_fname, tape_file)
return tape_blocks

def _get_word(self, num):
Expand Down Expand Up @@ -385,14 +385,22 @@ def test_no_options(self):
blocks = self._run(binfile)
self._check_tape(blocks, bin_data, binfile)

@patch.object(bin2tap, 'write_pzx', mock_write_tape)
def test_write_pzx(self):
bin_data = [1, 2, 3, 4, 5]
binfile = self.write_bin_file(bin_data, suffix='.bin')
pzxfile = 'out.pzx'
blocks = self._run(f'{binfile} {pzxfile}', pzxfile)
self._check_tape(blocks, bin_data, binfile, name=pzxfile[:-4])

def test_nonstandard_bin_name(self):
bin_data = [0]
binfile = self.write_bin_file(bin_data, suffix='.ram')
tapfile = '{0}.tap'.format(binfile)
blocks = self._run(binfile, tapfile)
self._check_tape(blocks, bin_data, binfile)

@patch.object(bin2tap, 'write_tap', mock_write_tap)
@patch.object(bin2tap, 'write_tap', mock_write_tape)
def test_bin_in_subdirectory(self):
tapfile = self.write_bin_file(suffix='.tap')
bin_data = [1]
Expand All @@ -402,7 +410,7 @@ def test_bin_in_subdirectory(self):
self.assertEqual(error, '')
self._check_tape(tape_blocks, bin_data, binfile)

@patch.object(bin2tap, 'write_tap', mock_write_tap)
@patch.object(bin2tap, 'write_tap', mock_write_tape)
def test_nonstandard_bin_name_in_subdirectory(self):
tapfile = self.write_bin_file(suffix='.ram.tap')
bin_data = [1]
Expand Down
Loading

0 comments on commit fec445c

Please sign in to comment.