module Z80::TAP
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
codeandorgattributes 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
TAPfile. Propertydatais a binary string containing the body data.- Header
A struct which represents the header chunk of a
TAPfile.Headerstruct properties:-
type- 0 - program, 1 - number array, 2 - character array, 3 - code. -
nameas a string. -
lengththe expected bytesize of the following body chunk as an integer. -
p1as an integer. -
p2as an integer.
For a program:
-
linereturns the starting line of a program. -
prog_lengthreturns the length in bytes of the program itself. -
vars_lengthreturns the length in bytes of the variables data.
For a number or character array:
-
array_namereturns the original variable name. -
array_headreturns the header octet of the original variable.
For a code:
-
address,addrandorgreturns 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:¶ ↑
-
:nameshould contain max 10 ascii characters. If not given, the base name of afilenamewill be used instead. -
:appendiftruethe 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