diff --git a/format/tap/tap.go b/format/tap/tap.go index b1e0c18f8..d4fdf463d 100644 --- a/format/tap/tap.go +++ b/format/tap/tap.go @@ -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) } diff --git a/format/tap/testdata/basic_prog1.fqtest b/format/tap/testdata/basic_prog1.fqtest index 19b51e22f..39791d82d 100644 --- a/format/tap/testdata/basic_prog1.fqtest +++ b/format/tap/testdata/basic_prog1.fqtest @@ -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) diff --git a/format/tzx/testdata/basic_prog1.fqtest b/format/tzx/testdata/basic_prog1.fqtest index d9b950d33..f1be031d8 100644 --- a/format/tzx/testdata/basic_prog1.fqtest +++ b/format/tzx/testdata/basic_prog1.fqtest @@ -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) diff --git a/format/tzx/tzx.go b/format/tzx/tzx.go index 98adb5a02..47ee98712 100644 --- a/format/tzx/tzx.go +++ b/format/tzx/tzx.go @@ -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.",