-
Notifications
You must be signed in to change notification settings - Fork 229
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
tzx: Add suport for ZX Spectrum TZX and TAP files #975
Changes from 13 commits
81b2304
88b6ded
e3c3d92
ffb5eb3
e526caf
6ac69e2
a241774
66345fe
e5be55c
113ca63
90de619
ee404b2
7816fe1
e345528
c93dc8b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -113,12 +113,14 @@ | |
|[`rtmp`](#rtmp) |Real-Time Messaging Protocol |<sub>`amf0` `mpeg_asc`</sub>| | ||
|`sll2_packet` |Linux cooked capture encapsulation v2 |<sub>`inet_packet`</sub>| | ||
|`sll_packet` |Linux cooked capture encapsulation |<sub>`inet_packet`</sub>| | ||
|[`tap`](#tap) |TAP tape format for ZX Spectrum computers |<sub></sub>| | ||
|`tar` |Tar archive |<sub>`probe`</sub>| | ||
|`tcp_segment` |Transmission control protocol segment |<sub></sub>| | ||
|`tiff` |Tag Image File Format |<sub>`icc_profile`</sub>| | ||
|[`tls`](#tls) |Transport layer security |<sub>`asn1_ber`</sub>| | ||
|`toml` |Tom's Obvious, Minimal Language |<sub></sub>| | ||
|[`tzif`](#tzif) |Time Zone Information Format |<sub></sub>| | ||
|[`tzx`](#tzx) |TZX tape format for ZX Spectrum computers |<sub>`tap`</sub>| | ||
|`udp_datagram` |User datagram protocol |<sub>`udp_payload`</sub>| | ||
|`vorbis_comment` |Vorbis comment |<sub>`flac_picture`</sub>| | ||
|`vorbis_packet` |Vorbis packet |<sub>`vorbis_comment`</sub>| | ||
|
@@ -137,7 +139,7 @@ | |
|`ip_packet` |Group |<sub>`icmp` `icmpv6` `tcp_segment` `udp_datagram`</sub>| | ||
|`link_frame` |Group |<sub>`bsd_loopback_frame` `ether8023_frame` `ipv4_packet` `ipv6_packet` `sll2_packet` `sll_packet`</sub>| | ||
|`mp3_frame_tags` |Group |<sub>`mp3_frame_vbri` `mp3_frame_xing`</sub>| | ||
|`probe` |Group |<sub>`adts` `aiff` `apple_bookmark` `ar` `avi` `avro_ocf` `bitcoin_blkdat` `bplist` `bzip2` `caff` `elf` `fit` `flac` `gif` `gzip` `html` `jp2c` `jpeg` `json` `jsonl` `leveldb_table` `luajit` `macho` `macho_fat` `matroska` `moc3` `mp3` `mp4` `mpeg_ts` `nes` `ogg` `opentimestamps` `pcap` `pcapng` `png` `tar` `tiff` `toml` `tzif` `wasm` `wav` `webp` `xml` `yaml` `zip`</sub>| | ||
|`probe` |Group |<sub>`adts` `aiff` `apple_bookmark` `ar` `avi` `avro_ocf` `bitcoin_blkdat` `bplist` `bzip2` `caff` `elf` `fit` `flac` `gif` `gzip` `html` `jp2c` `jpeg` `json` `jsonl` `leveldb_table` `luajit` `macho` `macho_fat` `matroska` `moc3` `mp3` `mp4` `mpeg_ts` `nes` `ogg` `opentimestamps` `pcap` `pcapng` `png` `tar` `tiff` `toml` `tzif` `tzx` `wasm` `wav` `webp` `xml` `yaml` `zip`</sub>| | ||
|`tcp_stream` |Group |<sub>`dns_tcp` `rtmp` `tls`</sub>| | ||
|`udp_payload` |Group |<sub>`dns`</sub>| | ||
|
||
|
@@ -1209,6 +1211,26 @@ fq '.tcp_connections[] | select(.server.port=="rtmp") | d' file.cap | |
- https://rtmp.veriskope.com/docs/spec/ | ||
- https://rtmp.veriskope.com/pdf/video_file_format_spec_v10.pdf | ||
|
||
## tap | ||
TAP tape format for ZX Spectrum computers. | ||
|
||
The TAP- (and BLK-) format is nearly a direct copy of the data that is stored | ||
in real tapes, as it is written by the ROM save routine of the ZX-Spectrum. | ||
A TAP file is simply one data block or a group of 2 or more data blocks, one | ||
followed after the other. The TAP file may be empty. | ||
|
||
You will often find this format embedded inside the TZX tape format. | ||
|
||
The default file extension is `.tap`. | ||
|
||
### Authors | ||
|
||
- Michael R. Cook [email protected], original author | ||
|
||
### References | ||
|
||
- https://worldofspectrum.net/zx-modules/fileformats/tapformat.html | ||
|
||
## tls | ||
Transport layer security. | ||
|
||
|
@@ -1378,6 +1400,27 @@ fq '.v2plusdatablock.leap_second_records | length' tziffile | |
### References | ||
- https://datatracker.ietf.org/doc/html/rfc8536 | ||
|
||
## tzx | ||
TZX tape format for ZX Spectrum computers. | ||
|
||
`TZX` is a file format designed to preserve cassette tapes compatible with the | ||
ZX Spectrum computers, although some specialized versions of the format have | ||
been defined for other machines such as the Amstrad CPC and C64. | ||
|
||
The format was originally created by Tomaz Kac, who was maintainer until | ||
`revision 1.13`, before passing it to Martijn v.d. Heide. For a brief period | ||
the company Ramsoft became the maintainers, and created revision `v1.20`. | ||
|
||
The default file extension is `.tzx`. | ||
|
||
### Authors | ||
|
||
- Michael R. Cook [email protected], original author | ||
|
||
### References | ||
|
||
- https://worldofspectrum.net/TZXformat.html | ||
|
||
## wasm | ||
WebAssembly Binary Format. | ||
|
||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,139 @@ | ||||||
package tzx | ||||||
|
||||||
// https://worldofspectrum.net/zx-modules/fileformats/tapformat.html | ||||||
|
||||||
import ( | ||||||
"embed" | ||||||
|
||||||
"golang.org/x/text/encoding/charmap" | ||||||
|
||||||
"github.com/wader/fq/format" | ||||||
"github.com/wader/fq/pkg/decode" | ||||||
"github.com/wader/fq/pkg/interp" | ||||||
"github.com/wader/fq/pkg/scalar" | ||||||
) | ||||||
|
||||||
//go:embed tap.md | ||||||
var tapFS embed.FS | ||||||
|
||||||
func init() { | ||||||
interp.RegisterFormat( | ||||||
format.TAP, | ||||||
&decode.Format{ | ||||||
Description: "TAP tape format for ZX Spectrum computers", | ||||||
DecodeFn: tapDecoder, | ||||||
}) | ||||||
|
||||||
interp.RegisterFS(tapFS) | ||||||
} | ||||||
|
||||||
// The TAP- (and BLK-) format is nearly a direct copy of the data that is stored | ||||||
// in real tapes, as it is written by the ROM save routine of the ZX-Spectrum. | ||||||
// A TAP file is simply one data block or a group of 2 or more data blocks, one | ||||||
// followed after the other. The TAP file may be empty. | ||||||
func tapDecoder(d *decode.D) any { | ||||||
d.Endian = decode.LittleEndian | ||||||
|
||||||
d.FieldArray("blocks", func(d *decode.D) { | ||||||
for !d.End() { | ||||||
d.FieldStruct("block", func(d *decode.D) { | ||||||
decodeTapBlock(d) | ||||||
}) | ||||||
} | ||||||
}) | ||||||
return nil | ||||||
} | ||||||
|
||||||
func decodeTapBlock(d *decode.D) { | ||||||
// Length of the following data. | ||||||
length := d.FieldU16("length") | ||||||
|
||||||
// read header, fragment, or data block | ||||||
switch length { | ||||||
case 0: | ||||||
// fragment with no data | ||||||
case 1: | ||||||
d.FieldRawLen("data", 8) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Possible future change: Are these data fragments (and data below) something that can be joined into something decodable? if so fq can append things together and decode a "sub"-buffer of some format or just provide a large raw field |
||||||
case 19: | ||||||
d.FieldStruct("header", func(d *decode.D) { | ||||||
decodeHeader(d) | ||||||
}) | ||||||
default: | ||||||
d.FieldStruct("data", func(d *decode.D) { | ||||||
decodeDataBlock(d, length) | ||||||
}) | ||||||
} | ||||||
} | ||||||
|
||||||
// decodes the different types of 19-byte header blocks. | ||||||
func decodeHeader(d *decode.D) { | ||||||
// Always 0: byte indicating a standard ROM loading header | ||||||
d.FieldU8("flag", scalar.UintMapSymStr{0: "standard_speed_data"}) | ||||||
// Header type | ||||||
dataType := d.FieldU8("data_type", scalar.UintMapSymStr{ | ||||||
0x00: "program", | ||||||
0x01: "numeric", | ||||||
0x02: "alphanumeric", | ||||||
0x03: "data", | ||||||
}) | ||||||
// Loading name of the program. Filled with spaces (0x20) to 10 characters. | ||||||
d.FieldStr("program_name", 10, charmap.ISO8859_1) | ||||||
|
||||||
switch dataType { | ||||||
case 0: | ||||||
// Length of data following the header = length of BASIC program + variables. | ||||||
d.FieldU16("data_length") | ||||||
// LINE parameter of SAVE command. Value 32768 means "no auto-loading". | ||||||
// 0..9999 are valid line numbers. | ||||||
d.FieldU16("auto_start_line") | ||||||
// Length of BASIC program; | ||||||
// remaining bytes ([data length] - [program length]) = offset of variables. | ||||||
d.FieldU16("program_length") | ||||||
case 1: | ||||||
// Length of data following the header = length of number array * 5 + 3. | ||||||
d.FieldU16("data_length") | ||||||
// Unused byte. | ||||||
d.FieldU8("unused0") | ||||||
// (1..26 meaning A..Z) + 128. | ||||||
d.FieldU8("variable_name", scalar.UintHex) | ||||||
// UnusedWord: 32768. | ||||||
d.FieldU16("unused1") | ||||||
case 2: | ||||||
// Length of data following the header = length of string array + 3. | ||||||
d.FieldU16("data_length") | ||||||
// Unused byte. | ||||||
d.FieldU8("unused0") | ||||||
// (1..26 meaning A$..Z$) + 192. | ||||||
d.FieldU8("variable_name", scalar.UintHex) | ||||||
// UnusedWord: 32768. | ||||||
d.FieldU16("unused1") | ||||||
case 3: | ||||||
// Length of data following the header, in case of a SCREEN$ header = 6912. | ||||||
d.FieldU16("data_length") | ||||||
// In case of a SCREEN$ header = 16384. | ||||||
d.FieldU16("start_address", scalar.UintHex) | ||||||
// UnusedWord: 32768. | ||||||
d.FieldU16("unused") | ||||||
default: | ||||||
d.Fatalf("invalid TAP header type, got: %d", dataType) | ||||||
} | ||||||
|
||||||
// Simply all bytes XORed (including flag byte). | ||||||
d.FieldU8("checksum", scalar.UintHex) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fq has some support for checksums if you like to add it Lines 232 to 233 in cdba38d
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not immediately obvious to me how I'm going implement this, so I won't get this done tonight. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok but no worries, is up to you. Could be done later if it's something you want There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought I'd have a quick look before I go to bed and put something together. |
||||||
} | ||||||
|
||||||
func decodeDataBlock(d *decode.D, length uint64) { | ||||||
// flag indicating the type of data block, usually 255 (standard speed data) | ||||||
d.FieldU8("flag", scalar.UintFn(func(s scalar.Uint) (scalar.Uint, error) { | ||||||
if s.Actual == 0xFF { | ||||||
s.Sym = "standard_speed_data" | ||||||
} else { | ||||||
s.Sym = "custom_data_block" | ||||||
} | ||||||
return s, nil | ||||||
})) | ||||||
// The essential data: length minus the flag/checksum bytes (may be empty) | ||||||
d.FieldRawLen("data", int64(length-2)*8) | ||||||
// Simply all bytes (including flag byte) XORed | ||||||
d.FieldU8("checksum", scalar.UintHex) | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
The TAP- (and BLK-) format is nearly a direct copy of the data that is stored | ||
in real tapes, as it is written by the ROM save routine of the ZX-Spectrum. | ||
A TAP file is simply one data block or a group of 2 or more data blocks, one | ||
followed after the other. The TAP file may be empty. | ||
|
||
You will often find this format embedded inside the TZX tape format. | ||
|
||
The default file extension is `.tap`. | ||
|
||
### Authors | ||
|
||
- Michael R. Cook [email protected], original author | ||
|
||
### References | ||
|
||
- https://worldofspectrum.net/zx-modules/fileformats/tapformat.html | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe you want to add a author section to tap.md and tzx.md? ex https://github.com/wader/fq/blob/master/format/fit/fit.md?plain=1#L14-L15 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
### basic_prog1.tap | ||
|
||
The `basic_prog1.tap` test file was created directory from the FUSE emulator. | ||
|
||
Inside the emulated ZX Spectrum a BASIC program was created: | ||
|
||
``` | ||
10 PRINT "fq is the best!" | ||
20 GOTO 10 | ||
``` | ||
|
||
and saved to tape: | ||
|
||
``` | ||
SAVE "fqTestProg", LINE 10 | ||
``` | ||
|
||
Then from FUSE select the menu item `Media > Tape > Save As..`. | ||
|
||
Any BASIC, machine code, screen image, or other data, can be saved directly | ||
using the `SAVE` command. Further instructions can be found here: | ||
https://worldofspectrum.org/ZXBasicManual/zxmanchap20.html |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
$ fq -d tap dv basic_prog1.tap | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Possible to add some instruction how the tap and tzx test files were created? if very simple just a comment in the .fqtest-file otherwise maybe a README.md in testdata? |
||
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|.{}: basic_prog1.tap (tap) 0x0-0x3f (63) | ||
| | | blocks[0:2]: 0x0-0x3f (63) | ||
| | | [0]{}: block 0x0-0x15 (21) | ||
0x00|13 00 |.. | length: 19 0x0-0x2 (2) | ||
| | | header{}: 0x2-0x15 (19) | ||
0x00| 00 | . | flag: "standard_speed_data" (0) 0x2-0x3 (1) | ||
0x00| 00 | . | data_type: "program" (0) 0x3-0x4 (1) | ||
0x00| 66 71 54 65 73 74 50 72 6f 67 | fqTestProg | program_name: "fqTestProg" 0x4-0xe (10) | ||
0x00| 26 00| &.| data_length: 38 0xe-0x10 (2) | ||
0x10|0a 00 |.. | auto_start_line: 10 0x10-0x12 (2) | ||
0x10| 26 00 | &. | program_length: 38 0x12-0x14 (2) | ||
0x10| 01 | . | checksum: 0x1 0x14-0x15 (1) | ||
| | | [1]{}: block 0x15-0x3f (42) | ||
0x10| 28 00 | (. | length: 40 0x15-0x17 (2) | ||
| | | data{}: 0x17-0x3f (40) | ||
0x10| ff | . | flag: "standard_speed_data" (255) 0x17-0x18 (1) | ||
0x10| 00 0a 14 00 20 f5 22 66| .... ."f| data: raw bits 0x18-0x3e (38) | ||
0x20|71 20 69 73 20 74 68 65 20 62 65 73 74 21 22 0d|q is the best!".| | ||
0x30|00 14 0a 00 ec 31 30 0e 00 00 0a 00 00 0d |.....10....... | | ||
0x30| b6| | .|| checksum: 0xb6 0x3e-0x3f (1) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, on running this all blocks are still being decoded. I'll take another look at this tonight. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After a quick debug I notice that the
Is my implementation missing something? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeap see my comment for |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
### basic_prog1.tzx | ||
|
||
The `basic_prog1.tzx` test file was created directory from the FUSE emulator. | ||
|
||
Inside the emulated ZX Spectrum a BASIC program was created: | ||
|
||
``` | ||
10 PRINT "fq is the best!" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🥳 |
||
20 GOTO 10 | ||
``` | ||
|
||
and saved to tape: | ||
|
||
``` | ||
SAVE "fqTestProg", LINE 10 | ||
``` | ||
|
||
Then from FUSE select the menu item `Media > Tape > Save As..`. | ||
|
||
Any BASIC, machine code, screen image, or other data, can be saved directly | ||
using the `SAVE` command. Further instructions can be found here: | ||
https://worldofspectrum.org/ZXBasicManual/zxmanchap20.html | ||
|
||
|
||
#### Archive Info | ||
|
||
The FUSE emulator is not able to add the tape metadata. As this tape block is | ||
very simple, it was added manually using a Hex editor. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add this and i think the
read_one_block
option should work:It's how the fq internals know the type and also default values
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did initially think to add this, but then figured, it's false by default so no need to add it :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aha :) i've thought about making
Format
generic somehow then the in/out types could be passed as use zero value... but maybe not worth it? maybe will refactor this in the future