module Z80::TZX
Adds the TAP
format support to your program.
Example:
puts Z80::TAP.parse_file("examples/calculator.tap").to_a Program: "calculator" LINE 10 (226/226) Bytes: "calculator" CODE 32768,61 # convert a TZX file to a TAP file (may not work for custom loaders) Z80::TAP.parse_file('foobar.tzx') {|t| t.save_tap('foobar', append:true) }
Program.import_file
will make use of Z80::TAP.read_data
. Pass additional :index
=> n
argument to Program.import_file
to choose n'th chunk from a tap file.
import_file 'somefile.tap', :index => 2
You may
include Z80::TAP
in your Z80
Program class and instances of it will be enriched by the three additional methods:
In fact this mixin may be used on any class as long as either:
-
Your class have
code
andorg
attributes producing a binary string and a starting address of a program. -
Your class provides your own implementation of
to_tap_chunk
.
The method to_tap_chunk
may be overridden to create other TAP
file types. The default implementation creates a TYPE_CODE
file based on code
and org
properties of the base class. The custom implementation may use one of the: TAP::HeaderBody.new_code
, TAP::HeaderBody.new_program
, TAP::HeaderBody.new_var_array
helper methods to construct a proper TAP::HeaderBody
instance.
See:¶ ↑
faqwiki.zxnet.co.uk/wiki/TAP_format
|------ Spectrum-generated data -------| |---------| 13 00 00 03 52 4f 4d 7x20 02 00 00 00 00 80 f1 04 00 ff f3 af a3 ^^^^^...... first block is 19 bytes (17 bytes+flag+checksum) ^^... flag byte (A reg, 00 for headers, ff for data blocks) ^^ first byte of header, indicating a code block file name ..^^^^^^^^^^^^^ header info ..............^^^^^^^^^^^^^^^^^ checksum of header .........................^^ length of second block ........................^^^^^ flag byte ...........................................^^ first two bytes of rom .................................^^^^^ checksum (checkbittoggle would be a better name!).............^^
Constants
- Body
A struct which represents the body chunk of a
TAP
file. Propertydata
is a binary string containing the body data.- Header
A struct which represents the header chunk of a
TAP
file.Header
struct properties:-
type
- 0 - program, 1 - number array, 2 - character array, 3 - code. -
name
as a string. -
length
the expected bytesize of the following body chunk as an integer. -
p1
as an integer. -
p2
as an integer.
For a program:
-
line
returns the starting line of a program. -
prog_length
returns the length in bytes of the program itself. -
vars_length
returns the length in bytes of the variables data.
For a number or character array:
-
array_name
returns the original variable name. -
array_head
returns the header octet of the original variable.
For a code:
-
address
,addr
andorg
returns the original code address.
-
- TYPE_CHAR_ARRAY
- TYPE_CODE
- TYPE_NUMBER_ARRAY
- TYPE_PROGRAM
Public Class Methods
Returns an Enumerator of TAP::HeaderBody
chunks representing segments of a TAP
file
. Optionally unwraps TZX
headers.
Pass a block
to visit each chunk
.
# File lib/z80/tap.rb, line 380 def parse_file(filename, &block) tap = File.open(filename, 'rb') {|f| f.read } TAP.parse_tap(tap, filename, &block) end
Returns an Enumerator of TAP::HeaderBody
chunks representing segments of a TAP
blob. Optionally unwraps TZX
headers.
The tap
argument must be a binary string. Pass a block
to visit each chunk
. Optionally pass a file
name for error messages.
# File lib/z80/tap.rb, line 392 def parse_tap(tap, file='-', &block) tap, is_tzx = TAP.unpack_from_tzx_header tap, file enu = ::Enumerator.new do |y| header = nil loop do if is_tzx tap = TAP.unpack_from_tzx_chunk tap, file end size, tap = tap.unpack('va*') break if size.nil? && tap.empty? chunk, tap = tap.unpack("a#{size}a*") raise TapeError, "Invalid TAP file checksum: `#{file}'." unless cksum(chunk) raise TapeError, "TAP block too short: `#{file}'." unless chunk.bytesize == size type, data, _cksum = chunk.unpack("Ca#{size-2}C") case type when 0x00 raise TapeError, "Invalid TAP header length: `#{file}'." unless data.bytesize == 17 header = Header.new(*data.unpack('CA10v3')) when 0xff unless header.nil? data, = data.unpack("a#{header.length}") if data.bytesize >= header.length raise TapeError, "TAP bytes length doesn't match length in header: `#{file}'." unless data.bytesize == header.length end chunk = HeaderBody.new header, Body.new(data) header = nil y << chunk else raise TapeError, "Invalid TAP file chunk: `#{file}'." end end end if block_given? enu.each(&block) else enu end end
Reads a TAP::HeaderBody
chunk from a TAP
file.
Pass additional :name
argument to search for the header name or a :index
=> n
argument to choose the n'th chunk (1-based) from a file.
Pass a block to visit a chunk.
# File lib/z80/tap.rb, line 359 def read_chunk(filename, name:nil, index:nil) parser = parse_file(filename) chunk, = if name.nil? index = 1 if index.nil? parser.each.with_index(1).find { |chunk, i| i == index } else parser.find { |chunk| name === chunk.header.name } end return unless chunk if block_given? yield chunk else chunk end end
Reads a data chunk from a TAP
file. Returns a binary string.
Program.import_file
uses this method to read from a TAP
file.
See read_chunk
for opts
.
# File lib/z80/tap.rb, line 339 def read_data(filename, **opts) TAP.read_chunk(filename, **opts) do |chunk| if chunk.header $stderr.puts "Importing: `#{filename}': (#{chunk.header.name})" else $stderr.puts "Importing: `#{filename}': headerless chunk" end return chunk.body.data end raise "Chunk: #{index} not found in a TAP file: `#{filename}`" end
Public Instance Methods
Saves self in a TAP
file.
The tap data is being generated by to_tap_chunk
.
filename
specifies the file name to save to. The “.tap” extension may be omitted.
Options:¶ ↑
-
:name
should contain max 10 ascii characters. If not given, the base name of afilename
will be used instead. -
:append
iftrue
the data will be appended to the file. Otherwise the file is being truncated.
Any additional option will be passed to the to_tap_chunk
method.
# File lib/z80/tap.rb, line 77 def save_tap(filename, append:false, name:nil, **opts) name = File.basename(filename, '.tap') unless name to_tap_chunk(name, **opts).save_tap filename, append:append end
Produces a TAP
blob as a binary string from self.
A sugar for calling TAP::HeaderBody#to_tap
method on the result produced by to_tap_chunk
.
name
should contain max 10 ascii characters. Any options given will be passed to the to_tap_chunk
method.
# File lib/z80/tap.rb, line 88 def to_tap(name, **opts) to_tap_chunk(name, **opts).to_tap end
Creates a TAP::HeaderBody
chunk from self.
By default it uses Z80#code
and the Z80#org
to produce the tap data.
This method is used by to_tap
and save_tap
.
name
should contain max 10 ascii characters. Optionally org
may be given to override the starting code address.
# File lib/z80/tap.rb, line 100 def to_tap_chunk(name, org:nil) HeaderBody.new_code(name, code, org||self.org) end