1
1
mirror of https://github.com/wader/fq.git synced 2024-11-26 10:33:53 +03:00

Merge pull request #990 from mrcook/tzx-updates

TZX updates - mostly for easier JSON parsing
This commit is contained in:
Mattias Wadman 2024-08-19 14:35:17 +02:00 committed by GitHub
commit 1fac9516bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 134 additions and 101 deletions

View File

@ -6,6 +6,7 @@ import (
"bufio"
"bytes"
"embed"
"fmt"
"golang.org/x/text/encoding/charmap"
@ -54,9 +55,11 @@ func decodeTapBlock(d *decode.D) {
// read header, fragment, or data block
switch length {
case 0:
// fragment with no data
d.Fatalf("TAP fragments with 0 bytes are not supported")
case 1:
d.FieldRawLen("data", 8)
d.FieldStruct("data", func(d *decode.D) {
d.FieldRawLen("bytes", 8)
})
case 19:
d.FieldStruct("header", func(d *decode.D) {
decodeHeader(d)
@ -72,15 +75,34 @@ func decodeTapBlock(d *decode.D) {
func decodeHeader(d *decode.D) {
blockStartPosition := d.Pos()
// Always 0: byte indicating a standard ROM loading header
d.FieldU8("flag", scalar.UintMapSymStr{0: "standard_speed_data"})
// flag indicating the type of header block, usually 0 (standard speed data)
d.FieldU8("flag", scalar.UintFn(func(s scalar.Uint) (scalar.Uint, error) {
if s.Actual == 0x00 {
s.Sym = "standard_speed_data"
} else {
s.Sym = "custom_data_block"
}
return s, nil
}))
// Header type
dataType := d.FieldU8("data_type", scalar.UintMapSymStr{
0x00: "program",
0x01: "numeric",
0x02: "alphanumeric",
0x03: "data",
})
dataType := d.FieldU8("data_type", scalar.UintFn(func(s scalar.Uint) (scalar.Uint, error) {
switch s.Actual {
case 0x00:
s.Sym = "program"
case 0x01:
s.Sym = "numeric"
case 0x02:
s.Sym = "alphanumeric"
case 0x03:
s.Sym = "data"
default:
// unofficial header types
s.Sym = fmt.Sprintf("unknown%02X", s.Actual)
}
return s, nil
}))
// Loading name of the program. Filled with spaces (0x20) to 10 characters.
d.FieldStr("program_name", 10, charmap.ISO8859_1)
@ -120,7 +142,10 @@ func decodeHeader(d *decode.D) {
// UnusedWord: 32768.
d.FieldU16("unused")
default:
d.Fatalf("invalid TAP header type, got: %d", dataType)
// Unofficial header types
d.FieldU16("data_length")
d.FieldU16("unknown1", scalar.UintHex)
d.FieldU16("unknown2", scalar.UintHex)
}
// Simply all bytes XORed (including flag byte).
@ -140,7 +165,8 @@ func decodeDataBlock(d *decode.D, length uint64) {
return s, nil
}))
// The essential data: length minus the flag/checksum bytes (may be empty)
d.FieldRawLen("data", int64(length-2)*8)
d.FieldRawLen("bytes", int64(length-2)*8)
// Simply all bytes (including flag byte) XORed
d.FieldU8("checksum", d.UintValidate(calculateChecksum(d, blockStartPosition, d.Pos()-blockStartPosition)), scalar.UintHex)
}

View File

@ -15,7 +15,7 @@ $ fq -d tap dv basic_prog1.tap
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)
0x10| 00 0a 14 00 20 f5 22 66| .... ."f| bytes: 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 (valid) 0x3e-0x3f (1)

View File

@ -5,77 +5,80 @@ $ fq -d tzx dv basic_prog1.tzx
0x00| 14 | . | minor_version: 20 0x9-0xa (1)
| | | blocks[0:3]: 0xa-0xcd (195)
| | | [0]{}: block 0xa-0x88 (126)
0x00| 32 | 2 | type: "archive_info" (50) 0xa-0xb (1)
0x00| 7b 00 | {. | length: 123 0xb-0xd (2)
0x00| 09 | . | count: 9 0xd-0xe (1)
| | | archive_info[0:9]: 0xe-0x88 (122)
| | | [0]{}: entry 0xe-0x1a (12)
0x00| 00 | . | id: "title" (0) 0xe-0xf (1)
0x00| 0a| .| length: 10 0xf-0x10 (1)
0x10|66 71 74 65 73 74 70 72 6f 67 |fqtestprog | value: "fqtestprog" 0x10-0x1a (10)
| | | [1]{}: entry 0x1a-0x21 (7)
0x10| 01 | . | id: "publisher" (1) 0x1a-0x1b (1)
0x10| 05 | . | length: 5 0x1b-0x1c (1)
0x10| 77 61 64 65| wade| value: "wader" 0x1c-0x21 (5)
| | | archive_info{}: 0xa-0x88 (126)
0x00| 32 | 2 | type: "archive_info" (50) 0xa-0xb (1)
0x00| 7b 00 | {. | length: 123 0xb-0xd (2)
0x00| 09 | . | count: 9 0xd-0xe (1)
| | | entries[0:9]: 0xe-0x88 (122)
| | | [0]{}: entry 0xe-0x1a (12)
0x00| 00 | . | id: "title" (0) 0xe-0xf (1)
0x00| 0a| .| length: 10 0xf-0x10 (1)
0x10|66 71 74 65 73 74 70 72 6f 67 |fqtestprog | value: "fqtestprog" 0x10-0x1a (10)
| | | [1]{}: entry 0x1a-0x21 (7)
0x10| 01 | . | id: "publisher" (1) 0x1a-0x1b (1)
0x10| 05 | . | length: 5 0x1b-0x1c (1)
0x10| 77 61 64 65| wade| value: "wader" 0x1c-0x21 (5)
0x20|72 |r |
| | | [2]{}: entry 0x21-0x32 (17)
0x20| 02 | . | id: "author" (2) 0x21-0x22 (1)
0x20| 0f | . | length: 15 0x22-0x23 (1)
0x20| 4d 69 63 68 61 65 6c 20 52 2e 20 43 6f| Michael R. Co| value: "Michael R. Cook" 0x23-0x32 (15)
| | | [2]{}: entry 0x21-0x32 (17)
0x20| 02 | . | id: "author" (2) 0x21-0x22 (1)
0x20| 0f | . | length: 15 0x22-0x23 (1)
0x20| 4d 69 63 68 61 65 6c 20 52 2e 20 43 6f| Michael R. Co| value: "Michael R. Cook" 0x23-0x32 (15)
0x30|6f 6b |ok |
| | | [3]{}: entry 0x32-0x38 (6)
0x30| 03 | . | id: "year" (3) 0x32-0x33 (1)
0x30| 04 | . | length: 4 0x33-0x34 (1)
0x30| 32 30 32 34 | 2024 | value: "2024" 0x34-0x38 (4)
| | | [4]{}: entry 0x38-0x41 (9)
0x30| 04 | . | id: "language" (4) 0x38-0x39 (1)
0x30| 07 | . | length: 7 0x39-0x3a (1)
0x30| 45 6e 67 6c 69 73| Englis| value: "English" 0x3a-0x41 (7)
| | | [3]{}: entry 0x32-0x38 (6)
0x30| 03 | . | id: "year" (3) 0x32-0x33 (1)
0x30| 04 | . | length: 4 0x33-0x34 (1)
0x30| 32 30 32 34 | 2024 | value: "2024" 0x34-0x38 (4)
| | | [4]{}: entry 0x38-0x41 (9)
0x30| 04 | . | id: "language" (4) 0x38-0x39 (1)
0x30| 07 | . | length: 7 0x39-0x3a (1)
0x30| 45 6e 67 6c 69 73| Englis| value: "English" 0x3a-0x41 (7)
0x40|68 |h |
| | | [5]{}: entry 0x41-0x4f (14)
0x40| 05 | . | id: "category" (5) 0x41-0x42 (1)
0x40| 0c | . | length: 12 0x42-0x43 (1)
0x40| 54 65 73 74 20 50 72 6f 67 72 61 6d | Test Program | value: "Test Program" 0x43-0x4f (12)
| | | [6]{}: entry 0x4f-0x5c (13)
0x40| 07| .| id: "loader" (7) 0x4f-0x50 (1)
0x50|0b |. | length: 11 0x50-0x51 (1)
0x50| 52 4f 4d 20 74 69 6d 69 6e 67 73 | ROM timings | value: "ROM timings" 0x51-0x5c (11)
| | | [7]{}: entry 0x5c-0x6e (18)
0x50| 08 | . | id: "origin" (8) 0x5c-0x5d (1)
0x50| 10 | . | length: 16 0x5d-0x5e (1)
0x50| 4f 72| Or| value: "Original release" 0x5e-0x6e (16)
| | | [5]{}: entry 0x41-0x4f (14)
0x40| 05 | . | id: "category" (5) 0x41-0x42 (1)
0x40| 0c | . | length: 12 0x42-0x43 (1)
0x40| 54 65 73 74 20 50 72 6f 67 72 61 6d | Test Program | value: "Test Program" 0x43-0x4f (12)
| | | [6]{}: entry 0x4f-0x5c (13)
0x40| 07| .| id: "loader" (7) 0x4f-0x50 (1)
0x50|0b |. | length: 11 0x50-0x51 (1)
0x50| 52 4f 4d 20 74 69 6d 69 6e 67 73 | ROM timings | value: "ROM timings" 0x51-0x5c (11)
| | | [7]{}: entry 0x5c-0x6e (18)
0x50| 08 | . | id: "origin" (8) 0x5c-0x5d (1)
0x50| 10 | . | length: 16 0x5d-0x5e (1)
0x50| 4f 72| Or| value: "Original release" 0x5e-0x6e (16)
0x60|69 67 69 6e 61 6c 20 72 65 6c 65 61 73 65 |iginal release |
| | | [8]{}: entry 0x6e-0x88 (26)
0x60| ff | . | id: "comment" (255) 0x6e-0x6f (1)
0x60| 18| .| length: 24 0x6f-0x70 (1)
0x70|54 5a 58 65 64 20 62 79 20 4d 69 63 68 61 65 6c|TZXed by Michael| value: "TZXed by Michael R. Cook" 0x70-0x88 (24)
| | | [8]{}: entry 0x6e-0x88 (26)
0x60| ff | . | id: "comment" (255) 0x6e-0x6f (1)
0x60| 18| .| length: 24 0x6f-0x70 (1)
0x70|54 5a 58 65 64 20 62 79 20 4d 69 63 68 61 65 6c|TZXed by Michael| value: "TZXed by Michael R. Cook" 0x70-0x88 (24)
0x80|20 52 2e 20 43 6f 6f 6b | R. Cook |
| | | [1]{}: block 0x88-0xa0 (24)
0x80| 10 | . | type: "standard_speed_data" (16) 0x88-0x89 (1)
0x80| e8 03 | .. | pause: 1000 0x89-0x8b (2)
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef| tap{}: (tap) 0x8b-0xa0 (21)
| | | blocks[0:1]: 0x8b-0xa0 (21)
| | | [0]{}: block 0x8b-0xa0 (21)
0x80| 13 00 | .. | length: 19 0x8b-0x8d (2)
| | | header{}: 0x8d-0xa0 (19)
0x80| 00 | . | flag: "standard_speed_data" (0) 0x8d-0x8e (1)
0x80| 00 | . | data_type: "program" (0) 0x8e-0x8f (1)
0x80| 66| f| program_name: "fqTestProg" 0x8f-0x99 (10)
| | | standard_speed_data{}: 0x88-0xa0 (24)
0x80| 10 | . | type: "standard_speed_data" (16) 0x88-0x89 (1)
0x80| e8 03 | .. | pause: 1000 0x89-0x8b (2)
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef| tap{}: (tap) 0x8b-0xa0 (21)
| | | blocks[0:1]: 0x8b-0xa0 (21)
| | | [0]{}: block 0x8b-0xa0 (21)
0x80| 13 00 | .. | length: 19 0x8b-0x8d (2)
| | | header{}: 0x8d-0xa0 (19)
0x80| 00 | . | flag: "standard_speed_data" (0) 0x8d-0x8e (1)
0x80| 00 | . | data_type: "program" (0) 0x8e-0x8f (1)
0x80| 66| f| program_name: "fqTestProg" 0x8f-0x99 (10)
0x90|71 54 65 73 74 50 72 6f 67 |qTestProg |
0x90| 26 00 | &. | data_length: 38 0x99-0x9b (2)
0x90| 0a 00 | .. | auto_start_line: 10 0x9b-0x9d (2)
0x90| 26 00 | &. | program_length: 38 0x9d-0x9f (2)
0x90| 01| .| checksum: 0x1 (valid) 0x9f-0xa0 (1)
0x90| 26 00 | &. | data_length: 38 0x99-0x9b (2)
0x90| 0a 00 | .. | auto_start_line: 10 0x9b-0x9d (2)
0x90| 26 00 | &. | program_length: 38 0x9d-0x9f (2)
0x90| 01| .| checksum: 0x1 (valid) 0x9f-0xa0 (1)
| | | [2]{}: block 0xa0-0xcd (45)
0xa0|10 |. | type: "standard_speed_data" (16) 0xa0-0xa1 (1)
0xa0| e8 03 | .. | pause: 1000 0xa1-0xa3 (2)
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef| tap{}: (tap) 0xa3-0xcd (42)
| | | blocks[0:1]: 0xa3-0xcd (42)
| | | [0]{}: block 0xa3-0xcd (42)
0xa0| 28 00 | (. | length: 40 0xa3-0xa5 (2)
| | | data{}: 0xa5-0xcd (40)
0xa0| ff | . | flag: "standard_speed_data" (255) 0xa5-0xa6 (1)
0xa0| 00 0a 14 00 20 f5 22 66 71 20| .... ."fq | data: raw bits 0xa6-0xcc (38)
| | | standard_speed_data{}: 0xa0-0xcd (45)
0xa0|10 |. | type: "standard_speed_data" (16) 0xa0-0xa1 (1)
0xa0| e8 03 | .. | pause: 1000 0xa1-0xa3 (2)
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef| tap{}: (tap) 0xa3-0xcd (42)
| | | blocks[0:1]: 0xa3-0xcd (42)
| | | [0]{}: block 0xa3-0xcd (42)
0xa0| 28 00 | (. | length: 40 0xa3-0xa5 (2)
| | | data{}: 0xa5-0xcd (40)
0xa0| ff | . | flag: "standard_speed_data" (255) 0xa5-0xa6 (1)
0xa0| 00 0a 14 00 20 f5 22 66 71 20| .... ."fq | bytes: raw bits 0xa6-0xcc (38)
0xb0|69 73 20 74 68 65 20 62 65 73 74 21 22 0d 00 14|is the best!"...|
0xc0|0a 00 ec 31 30 0e 00 00 0a 00 00 0d |...10....... |
0xc0| b6| | .| | checksum: 0xb6 (valid) 0xcc-0xcd (1)
0xc0| b6| | .| | checksum: 0xb6 (valid) 0xcc-0xcd (1)

View File

@ -97,7 +97,7 @@ func decodeBlock(d *decode.D) {
length := d.FieldU24("length") // Length of data that follows
// Data as in .TAP files
d.FieldRawLen("data", int64(length*8))
d.FieldRawLen("data", int64(length)*8)
},
// ID: 12h (18d) | Pure Tone
@ -131,7 +131,7 @@ func decodeBlock(d *decode.D) {
length := d.FieldU24("length") // Length of data that follows
// Data as in .TAP files
d.FieldRawLen("data", int64(length*8))
d.FieldRawLen("data", int64(length)*8)
},
// ID: 15h (21d) | Direct Recording
@ -147,7 +147,7 @@ func decodeBlock(d *decode.D) {
d.FieldU16("pause") // Pause after this block in milliseconds (ms.)
d.FieldU8("used_bits") // Used bits (samples) in last byte of data (1-8)
length := d.FieldU24("length") // Length of data that follows
d.FieldRawLen("data", int64(length*8)) // Samples data. Each bit represents a state on the EAR port
d.FieldRawLen("data", int64(length)*8) // Samples data. Each bit represents a state on the EAR port
},
// ID: 18h (24d) | CSW Recording
@ -165,12 +165,12 @@ func decodeBlock(d *decode.D) {
// Sampling rate
d.FieldU24("sample_rate")
// Compression type
d.FieldU8("compression_type", scalar.UintMapSymStr{0x01: "rle", 0x02: "zrle"})
d.FieldU8("compression_type", scalar.UintMapSymStr{0x00: "unknown", 0x01: "rle", 0x02: "zrle"})
// Number of stored pulses (after decompression)
d.FieldU32("stored_pulse_count")
// CSW data, encoded according to the CSW specification
d.FieldRawLen("data", int64(length*8))
d.FieldRawLen("data", int64(length)*8)
},
// ID: 19h (25d) | Generalized Data
@ -194,7 +194,7 @@ func decodeBlock(d *decode.D) {
// PilotStreams []PilotRLE // 0x12+ (2*NPP+1)*ASP - PRLE[TOTP] Pilot and sync data stream
// DataSymbols []Symbol // 0x12+ (TOTP>0)*((2*NPP+1)*ASP)+TOTP*3 - SYMDEF[ASD] Data symbols definition table
// DataStreams []uint8 // 0x12+ (TOTP>0)*((2*NPP+1)*ASP)+ TOTP*3+(2*NPD+1)*ASD - BYTE[DS] Data stream
d.FieldRawLen("data", int64(length*8))
d.FieldRawLen("data", int64(length)*8)
},
// ID: 20h (32d) | Pause Tape Command
@ -340,7 +340,7 @@ func decodeBlock(d *decode.D) {
count := d.FieldU8("count") // Number of entries in the archive info
// the archive strings
d.FieldArray("archive_info", func(d *decode.D) {
d.FieldArray("entries", func(d *decode.D) {
for i := uint64(0); i < count; i++ {
d.FieldStruct("entry", func(d *decode.D) {
d.FieldU8("id", scalar.UintMapSymStr{
@ -372,11 +372,11 @@ func decodeBlock(d *decode.D) {
for i := uint64(0); i < count; i++ {
d.FieldStruct("info", func(d *decode.D) {
// Hardware Type ID (computers, printers, mice, etc.)
typeId := d.FieldU8("type", hwInfoTypeMapper)
// Hardware ID (ZX81, Kempston Joystick, etc.)
d.FieldU8("id", hwInfoTypeIdMapper[typeId])
typeId := d.FieldU8("type_id", hwInfoTypes)
// Hardware Device ID (ZX81, Kempston Joystick, etc.)
d.FieldU8("device_id", hwInfoDevices[typeId])
// Hardware compatibility information
d.FieldU8("info_id", hwInfoIdMapper)
d.FieldU8("info_id", hwInfoCompatibilityInfo)
})
}
})
@ -387,9 +387,9 @@ func decodeBlock(d *decode.D) {
// some information written by a utility, extra settings required by a
// particular emulator, etc.
0x35: func(d *decode.D) {
d.FieldStr("identification", 10, charmap.ISO8859_1)
d.FieldStr("identification", 16, charmap.ISO8859_1)
length := d.FieldU32("length")
d.FieldRawLen("info", int64(length*8))
d.FieldRawLen("info", int64(length)*8)
},
// ID: 5Ah (90d) | Glue Block
@ -404,22 +404,26 @@ func decodeBlock(d *decode.D) {
0x5A: func(d *decode.D) {
// Value: { "XTape!",0x1A,MajR,MinR }
// Just skip these 9 bytes and you will end up on the next ID.
d.FieldRawLen("value", int64(9*8))
d.FieldRawLen("value", 9*8)
},
}
blockType := d.FieldU8("type", blockTypeMapper)
blockType := d.PeekUintBits(8)
// Deprecated block types: C64RomType, C64TurboData, EmulationInfo, Snapshot
if blockType == 0x16 || blockType == 0x17 || blockType == 0x34 || blockType == 0x40 {
d.Fatalf("deprecated block type encountered: %02x", blockType)
}
if fn, ok := blocks[blockType]; ok {
fn(d)
} else {
d.Fatalf("block type not valid, got: %02x", blockType)
}
blockLabel := blockTypeMapper[blockType]
d.FieldStruct(blockLabel, func(d *decode.D) {
d.FieldU8("type", blockTypeMapper)
if fn, ok := blocks[blockType]; ok {
fn(d)
} else {
d.Fatalf("block type not valid, got: %02x", blockType)
}
})
}
var blockTypeMapper = scalar.UintMapSymStr{
@ -454,7 +458,7 @@ var blockTypeMapper = scalar.UintMapSymStr{
0x5A: "glue_block",
}
var hwInfoTypeMapper = scalar.UintMapDescription{
var hwInfoTypes = scalar.UintMapDescription{
0x00: "Computers",
0x01: "External storage",
0x02: "ROM/RAM type add-ons",
@ -474,7 +478,7 @@ var hwInfoTypeMapper = scalar.UintMapDescription{
0x10: "Graphics",
}
var hwInfoTypeIdMapper = map[uint64]scalar.UintMapDescription{
var hwInfoDevices = map[uint64]scalar.UintMapDescription{
0x00: { // Computers
0x00: "ZX Spectrum 16k",
0x01: "ZX Spectrum 48k, Plus",
@ -647,7 +651,7 @@ var hwInfoTypeIdMapper = map[uint64]scalar.UintMapDescription{
},
}
var hwInfoIdMapper = scalar.UintMapDescription{
var hwInfoCompatibilityInfo = scalar.UintMapDescription{
00: "RUNS on this machine or with this hardware, but may or may not use the hardware or special features of the machine.",
01: "USES the hardware or special features of the machine, such as extra memory or a sound chip.",
02: "RUNS but it DOESN'T use the hardware or special features of the machine.",