Skip to content

Commit

Permalink
Make rzxinfo.py an official SkoolKit command
Browse files Browse the repository at this point in the history
  • Loading branch information
skoolkid committed Feb 14, 2024
1 parent 4a96d75 commit fa007fa
Show file tree
Hide file tree
Showing 11 changed files with 543 additions and 55 deletions.
26 changes: 26 additions & 0 deletions rzxinfo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env python3

# Copyright 2024 Richard Dymond (rjdymond@gmail.com)
#
# This file is part of SkoolKit.
#
# SkoolKit 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.
#
# SkoolKit 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
# SkoolKit. If not, see <http://www.gnu.org/licenses/>.

import sys

from skoolkit import rzxinfo, error, SkoolKitError

try:
rzxinfo.main(sys.argv[1:])
except SkoolKitError as e:
error(e.args[0])
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ packages = skoolkit
scripts =
bin2sna.py
bin2tap.py
rzxinfo.py
skool2asm.py
skool2bin.py
skool2ctl.py
Expand Down
103 changes: 57 additions & 46 deletions utils/rzxinfo.py → skoolkit/rzxinfo.py
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
#!/usr/bin/env python3
# Copyright 2024 Richard Dymond (rjdymond@gmail.com)
#
# This file is part of SkoolKit.
#
# SkoolKit 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.
#
# SkoolKit 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
# SkoolKit. If not, see <http://www.gnu.org/licenses/>.

import argparse
import os
import sys
import tempfile
import zlib

SKOOLKIT_HOME = os.environ.get('SKOOLKIT_HOME')
if not SKOOLKIT_HOME:
sys.stderr.write('SKOOLKIT_HOME is not set; aborting\n')
sys.exit(1)
if not os.path.isdir(SKOOLKIT_HOME):
sys.stderr.write(f'SKOOLKIT_HOME={SKOOLKIT_HOME}; directory not found\n')
sys.exit(1)
sys.path.insert(0, SKOOLKIT_HOME)

from skoolkit import error, get_dword, get_word
from skoolkit.snapshot import Snapshot
from skoolkit import VERSION, SkoolKitError, get_dword, get_word
from skoolkit.snapinfo import get_szx_machine_type, get_z80_machine_type
from skoolkit.snapshot import Snapshot

def _get_str(data, i):
def _get_str(data, i, max_len):
s = ''
while data[i]:
while data[i] and len(s) < max_len:
s += chr(data[i])
i += 1
return s
Expand All @@ -36,7 +41,7 @@ def _show_blocks(data, options):
block_len = get_dword(data, i + 1)
if block_id == 0x10:
print('Creator information:')
creator_id = _get_str(data, i + 5)
creator_id = _get_str(data, i + 5, 20)
vmajor = get_word(data, i + 25)
vminor = get_word(data, i + 27)
print(f' ID: {creator_id} {vmajor}.{vminor}')
Expand All @@ -47,33 +52,36 @@ def _show_blocks(data, options):
elif block_id == 0x30:
print('Snapshot:')
flags = data[i + 5]
ext = _get_str(data, i + 9)
ext = _get_str(data, i + 9, 4)
length = get_dword(data, i + 13)
print(f' Filename extension: {ext}')
print(f' Size: {length} bytes')
sdata = data[i + 17:i + block_len]
if flags & 1:
ext_sname = _get_str(sdata, 4)
ext_sname = _get_str(sdata, 4, len(sdata) - 4)
print(f' External snapshot: {ext_sname}')
else:
if flags & 2:
sdata = zlib.decompress(sdata)
with tempfile.NamedTemporaryFile(suffix=f'.{ext}', buffering=0) as f:
f.write(sdata)
snap = Snapshot.get(f.name)
if snap:
if snap.type == 'SNA':
if len(snap.tail) > 0xC000:
machine = '128K Spectrum'
else:
machine = '48K Spectrum'
elif snap.type == 'SZX':
machine = get_szx_machine_type(snap.header)
elif snap.type == 'Z80':
machine = get_z80_machine_type(snap.header)
start_addr = snap.pc
else:
machine = 'Unknown'
if snap:
if snap.type == 'SNA':
if len(snap.ram(-1)) == 0x20000:
machine = '128K Spectrum'
else:
machine = '48K Spectrum'
elif snap.type == 'SZX':
machine = get_szx_machine_type(snap.header)
elif snap.type == 'Z80':
machine = get_z80_machine_type(snap.header)
start_addr = 'Unknown'
print(f' Machine: {machine}')
print(f' Start address: {snap.pc}')
print(f' Start address: {start_addr}')
elif block_id == 0x80:
print('Input recording')
num_frames = get_dword(data, i + 5)
Expand Down Expand Up @@ -121,7 +129,7 @@ def _extract_snapshots(data, prefix):
if block_id == 0x30:
flags = data[i + 5]
if flags & 1 == 0:
ext = _get_str(data, i + 9).lower()
ext = _get_str(data, i + 9, 4).lower()
sdata = data[i + 17:i + block_len]
if flags & 2:
sdata = zlib.decompress(sdata)
Expand All @@ -138,24 +146,27 @@ def run(infile, options):
with open(infile, 'rb') as f:
data = f.read()
if data[:4] != b'RZX!' or len(data) < 10:
error('Not an RZX file')
raise SkoolKitError('Not an RZX file')
if options.extract:
_extract_snapshots(data, os.path.basename(infile))
else:
_show_blocks(data, options)

parser = argparse.ArgumentParser(
usage='%(prog)s [options] FILE',
description="Show the blocks in or extract snapshots from an RZX file.",
add_help=False
)
parser.add_argument('infile', help=argparse.SUPPRESS, nargs='?')
group = parser.add_argument_group('Options')
group.add_argument('--extract', action='store_true',
help="Extract snapshots.")
group.add_argument('--frames', action='store_true',
help="Show the contents of every frame.")
namespace, unknown_args = parser.parse_known_args()
if unknown_args or namespace.infile is None:
parser.exit(2, parser.format_help())
run(namespace.infile, namespace)
def main(args):
parser = argparse.ArgumentParser(
usage='rzxinfo.py [options] FILE',
description="Show the blocks in or extract the snapshots from an RZX file.",
add_help=False
)
parser.add_argument('infile', help=argparse.SUPPRESS, nargs='?')
group = parser.add_argument_group('Options')
group.add_argument('--extract', action='store_true',
help="Extract snapshots.")
group.add_argument('--frames', action='store_true',
help="Show the contents of every frame.")
group.add_argument('-V', '--version', action='version', version='SkoolKit {}'.format(VERSION),
help='Show SkoolKit version number and exit.')
namespace, unknown_args = parser.parse_known_args(args)
if unknown_args or namespace.infile is None:
parser.exit(2, parser.format_help())
run(namespace.infile, namespace)
2 changes: 2 additions & 0 deletions sphinx/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ Changelog

9.2b1
-----
* Added the :ref:`rzxinfo.py` command (for showing the blocks in or extracting
the snapshots from an RZX file)
* Added support to :ref:`tap2sna.py` for TZX block type 0x15 (direct recording)
* :ref:`tapinfo.py` now shows info for TZX block type 0x15 (direct recording)

Expand Down
25 changes: 25 additions & 0 deletions sphinx/source/commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,31 @@ block on the tape.
| 1.3.1 | New |
+---------+-------------------------------------------------------------------+

.. _rzxinfo.py:

rzxinfo.py
----------
`rzxinfo.py` shows the blocks in or extracts the snapshots from an RZX file.
For example::

$ rzxinfo.py game.rzx

To list the options supported by rzxinfo.py, run it with no arguments::

usage: rzxinfo.py [options] FILE

Show the blocks in or extract the snapshots from an RZX file.

Options:
--extract Extract snapshots.
--frames Show the contents of every frame.

+---------+---------+
| Version | Changes |
+=========+=========+
| 9.2 | New |
+---------+---------+

.. _skool2asm.py:

skool2asm.py
Expand Down
2 changes: 2 additions & 0 deletions sphinx/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,8 @@
'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),
('man/rzxinfo.py', 'rzxinfo.py',
'show the blocks in or extract the snapshots from an RZX file', _authors, 1),
('man/skool2asm.py', 'skool2asm.py',
'convert a skool file to ASM format', _authors, 1),
('man/skool2bin.py', 'skool2bin.py',
Expand Down
36 changes: 36 additions & 0 deletions sphinx/source/man/rzxinfo.py.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
:orphan:

==========
rzxinfo.py
==========

SYNOPSIS
========
``rzxinfo.py`` [options] FILE

DESCRIPTION
===========
``rzxinfo.py`` shows the blocks in or extracts the snapshots from an RZX file.

OPTIONS
=======
--extract
Extract snapshots.

--frames
Show the contents of every frame.

-V, --version
Show the SkoolKit version number and exit.

EXAMPLES
========
1. Show information on the blocks in ``game.rzx``:

|
| ``rzxinfo.py game.rzx``
2. Extract all the snapshots from ``game.rzx``:

|
| ``rzxinfo.py --extract game.rzx``
Loading

0 comments on commit fa007fa

Please sign in to comment.