diff --git a/projects/tzpuPico/tools/MZQD/BASIC_5Z008A_SideA.qdf b/projects/tzpuPico/tools/MZQD/BASIC_5Z008A_SideA.qdf new file mode 100644 index 0000000..8f8354b Binary files /dev/null and b/projects/tzpuPico/tools/MZQD/BASIC_5Z008A_SideA.qdf differ diff --git a/projects/tzpuPico/tools/MZQD/BASIC_5Z008A_SideB.qdf b/projects/tzpuPico/tools/MZQD/BASIC_5Z008A_SideB.qdf new file mode 100644 index 0000000..b10c00c Binary files /dev/null and b/projects/tzpuPico/tools/MZQD/BASIC_5Z008A_SideB.qdf differ diff --git a/projects/tzpuPico/tools/MZQD/MZQDTool b/projects/tzpuPico/tools/MZQD/MZQDTool new file mode 100755 index 0000000..eaa2077 Binary files /dev/null and b/projects/tzpuPico/tools/MZQD/MZQDTool differ diff --git a/projects/tzpuPico/tools/MZQD/MZQDTool.c b/projects/tzpuPico/tools/MZQD/MZQDTool.c new file mode 100755 index 0000000..fd30ca4 --- /dev/null +++ b/projects/tzpuPico/tools/MZQD/MZQDTool.c @@ -0,0 +1,366 @@ +/* ========================================================================= + * MZQDTool — Sharp MZ-700 Quick Disk (QD) image tool + * + * Creates and manages QD disk images for the MZ-700 Quick Disk system. + * Supports format, directory listing, and adding MZF files to QD images. + * + * Original V0.30 (c) 2002 by BKK + * Updated V1.00 (c) 2026 Philip Smart — rewritten with configurable disk + * name, fixed file ordering, correct CMT→QD header conversion, + * and robust error handling. + * + * QD disk image format (61455 bytes): + * + * Disk header: + * [00] [16 16] [A5] [block_count] [CRC: 43 52 43] + * + * Per file (2 blocks each: header + data): + * + * Header block: + * [00] [16 16] [A5] [00] [size_lo=40] [size_hi=00] + * [64 bytes: QDHeaderStr] + * [CRC: 43 52 43] + * + * Data block: + * [00] [16 16] [A5] [05] [size_lo] [size_hi] + * [file data] + * [CRC: 43 52 43] + * + * Usage: + * MZQDTool format [-o disk.qd] + * MZQDTool dir [-o disk.qd] + * MZQDTool add [-o disk.qd] + * ========================================================================= */ + +#define VERSION "1.00" + +#include +#include +#include +#include + +#define QDSIZE 61455 +#define MAXQDFILETYPES 12 +#define DEFAULTQDFILE "MZ700.qd" +#define CMTHDRSIZE 128 /* MZF/CMT file header size */ +#define QDHDRSIZE 64 /* QD file header size */ + +/* MZF/CMT tape file header (128 bytes) */ +struct CMTHeaderStr { + uint8_t Attribute; /* File type: 01=OBJ, 02=BTX, etc. */ + char Name[17]; /* Filename (CR-terminated, null-padded) */ + uint16_t Size; /* Data size (0 = use actual file size) */ + uint16_t LoadAddress; /* Load address (DTADR) */ + uint16_t ExecAddress; /* Execution address (EXADR) */ + char Comment[104]; /* Comment area */ +}; + +/* QD file header (64 bytes, stored in header block) */ +struct QDHeaderStr { + uint8_t Attribute; /* File type: 01=OBJ, 02=BTX, etc. */ + char Name[17]; /* Filename (CR-terminated, null-padded) */ + uint16_t Unknown; /* Reserved (usually 0) */ + uint16_t Size; /* Data size in bytes */ + uint16_t LoadAddress; /* Load address */ + uint16_t ExecAddress; /* Execution address */ + char Comment[38]; /* Comment (truncated from CMT's 104) */ +}; + +static const char *QDFileTypes[MAXQDFILETYPES] = { + "???", "OBJ", "BTX", "BSD", "BRD", "RB ", + "???", "LIB", "???", "???", "SYS", "GR " +}; + +static uint8_t QDArray[QDSIZE]; +static char qdFileName[256] = DEFAULTQDFILE; + +/* ------- Low-level QD block I/O helpers ------- */ + +static void write_sync(uint16_t *ptr) +{ + QDArray[(*ptr)++] = 0x00; /* BREAK */ + QDArray[(*ptr)++] = 0x16; /* SYNC */ + QDArray[(*ptr)++] = 0x16; /* SYNC */ + QDArray[(*ptr)++] = 0xA5; /* SHARP SYNC */ +} + +static void write_crc(uint16_t *ptr) +{ + QDArray[(*ptr)++] = 'C'; + QDArray[(*ptr)++] = 'R'; + QDArray[(*ptr)++] = 'C'; +} + +static int save_qd(void) +{ + FILE *f = fopen(qdFileName, "wb"); + if (!f) { + fprintf(stderr, "ERROR: Cannot write '%s'\n", qdFileName); + return 0; + } + fwrite(QDArray, QDSIZE, 1, f); + fclose(f); + return 1; +} + +static int load_qd(void) +{ + FILE *f = fopen(qdFileName, "rb"); + if (!f) { + fprintf(stderr, "ERROR: Cannot open '%s'\n", qdFileName); + return 0; + } + fread(QDArray, QDSIZE, 1, f); + fclose(f); + return 1; +} + +/* ------- Commands ------- */ + +/* Format: create an empty QD image with 0 files */ +static void QDFormat(void) +{ + uint16_t ptr = 0; + + printf("Formatting '%s' (%d bytes)\n", qdFileName, QDSIZE); + memset(QDArray, 0x00, QDSIZE); + + /* Disk header: sync + block_count(0) + CRC */ + write_sync(&ptr); + QDArray[ptr++] = 0x00; /* 0 blocks */ + write_crc(&ptr); + + if (!save_qd()) exit(1); + puts("Done."); +} + +/* Dir: list files on the QD */ +static void QDDir(void) +{ + uint16_t ptr, blockSize; + uint8_t blocks, i; + + if (!load_qd()) exit(1); + + /* Disk header: 4 sync bytes + block_count + 3 CRC */ + ptr = 4; + blocks = QDArray[ptr++]; + ptr += 3; /* skip CRC */ + + printf("Directory of '%s': %d block(s), %d file(s)\n\n", + qdFileName, blocks, blocks / 2); + + for (i = 0; i < blocks; i++) { + if (ptr + 7 >= QDSIZE) { + fprintf(stderr, "WARNING: Truncated QD image at block %d\n", i); + break; + } + ptr += 4; /* sync: 00 16 16 A5 */ + + uint8_t blockType = QDArray[ptr++]; + blockSize = QDArray[ptr] | (QDArray[ptr + 1] << 8); + ptr += 2; + + if (blockType == 0x00 && blockSize == QDHDRSIZE) { + /* File header block */ + struct QDHeaderStr *hdr = (struct QDHeaderStr *)&QDArray[ptr]; + uint8_t ft = hdr->Attribute; + if (ft >= MAXQDFILETYPES) ft = 0; + + /* Format name: stop at CR or end of field */ + char name[18]; + memcpy(name, hdr->Name, 17); + name[17] = '\0'; + for (int j = 0; j < 17; j++) { + if (name[j] == '\r' || name[j] == '\0') { name[j] = '\0'; break; } + } + + printf(" %-3s %-17s Size=%-6u Load=0x%04X Exec=0x%04X\n", + QDFileTypes[ft], name, hdr->Size, + hdr->LoadAddress, hdr->ExecAddress); + } else if (blockType == 0x05) { + /* Data block — skip silently */ + } else { + printf(" [block %d: type=0x%02X size=%u]\n", i, blockType, blockSize); + } + + ptr += blockSize; + ptr += 3; /* skip CRC */ + } + printf("\n%u bytes free\n", QDSIZE - ptr); +} + +/* Add: append an MZF file to the QD image */ +static void AddFileToQD(const char *mzfFileName) +{ + FILE *mzfFile; + uint16_t ptr, fileDataSize; + uint8_t blocks, i; + uint16_t blockSize; + struct CMTHeaderStr cmtHdr; + + if (!load_qd()) exit(1); + + /* Read block count and skip to end of existing data */ + ptr = 4; + blocks = QDArray[ptr]; + ptr = 5 + 3; /* past block_count + CRC */ + + for (i = 0; i < blocks; i++) { + ptr += 4; /* sync */ + ptr++; /* block type */ + blockSize = QDArray[ptr] | (QDArray[ptr + 1] << 8); + ptr += 2; + ptr += blockSize; + ptr += 3; /* CRC */ + } + + /* Open and read MZF file */ + mzfFile = fopen(mzfFileName, "rb"); + if (!mzfFile) { + fprintf(stderr, "ERROR: Cannot open '%s'\n", mzfFileName); + exit(1); + } + fseek(mzfFile, 0, SEEK_END); + long mzfTotalSize = ftell(mzfFile); + fseek(mzfFile, 0, SEEK_SET); + + if (mzfTotalSize < CMTHDRSIZE) { + fprintf(stderr, "ERROR: '%s' is too small for MZF format (%ld bytes)\n", + mzfFileName, mzfTotalSize); + fclose(mzfFile); + exit(1); + } + + fread(&cmtHdr, CMTHDRSIZE, 1, mzfFile); + fileDataSize = (uint16_t)(mzfTotalSize - CMTHDRSIZE); + + /* Check available space */ + uint32_t needed = (4+1+2+QDHDRSIZE+3) + (4+1+2+(uint32_t)fileDataSize+3); + if (ptr + needed > QDSIZE) { + fprintf(stderr, "ERROR: Not enough space (need %u, have %u)\n", + (unsigned)needed, QDSIZE - ptr); + fclose(mzfFile); + exit(1); + } + + /* ---- Write header block ---- */ + write_sync(&ptr); + QDArray[ptr++] = 0x00; /* block type: file header */ + QDArray[ptr++] = QDHDRSIZE; /* size low */ + QDArray[ptr++] = 0x00; /* size high */ + + struct QDHeaderStr *qdHdr = (struct QDHeaderStr *)&QDArray[ptr]; + memset(qdHdr, 0, QDHDRSIZE); + + /* Convert CMT header to QD header */ + qdHdr->Attribute = cmtHdr.Attribute; + if (qdHdr->Attribute == 0x05) + qdHdr->Attribute = 0x02; /* MZF tape type 05 → QD type 02 (BTX) */ + memcpy(qdHdr->Name, cmtHdr.Name, 17); + qdHdr->Unknown = 0; + + /* + * Size field handling: + * - If MZF Size > 0: use it directly (normal file) + * - If MZF Size == 0: use actual file data size (convention for + * ROM images where Size=0 means "whole file"). The QD header + * MUST have the real size — the QD loader uses it to determine + * how many bytes to read. + */ + if (cmtHdr.Size != 0) + qdHdr->Size = cmtHdr.Size; + else + qdHdr->Size = fileDataSize; + + qdHdr->LoadAddress = cmtHdr.LoadAddress; + qdHdr->ExecAddress = cmtHdr.ExecAddress; + memcpy(qdHdr->Comment, cmtHdr.Comment, 38); + + ptr += QDHDRSIZE; + write_crc(&ptr); + + /* ---- Write data block ---- */ + write_sync(&ptr); + QDArray[ptr++] = 0x05; /* block type: file data */ + QDArray[ptr++] = fileDataSize & 0xFF; + QDArray[ptr++] = (fileDataSize >> 8) & 0xFF; + fread(&QDArray[ptr], 1, fileDataSize, mzfFile); + ptr += fileDataSize; + write_crc(&ptr); + + fclose(mzfFile); + + /* Update block count (+2: one header block, one data block) */ + QDArray[4] = blocks + 2; + + /* Format name for display */ + char name[18]; + memcpy(name, cmtHdr.Name, 17); + name[17] = '\0'; + for (int j = 0; j < 17; j++) { + if (name[j] == '\r' || name[j] == '\0') { name[j] = '\0'; break; } + } + + printf(" Added: type=0x%02X name=\"%s\" size=%u load=0x%04X exec=0x%04X\n", + qdHdr->Attribute, name, qdHdr->Size, + qdHdr->LoadAddress, qdHdr->ExecAddress); + printf(" QD: %d blocks, %u bytes free\n", blocks + 2, QDSIZE - ptr); + + if (!save_qd()) exit(1); +} + +/* ------- Usage and main ------- */ + +static void usage(void) +{ + printf("Usage:\n"); + printf(" MZQDTool format [-o disk.qd] Format empty QD image\n"); + printf(" MZQDTool dir [-o disk.qd] List QD directory\n"); + printf(" MZQDTool add [-o disk.qd] Add MZF file to QD\n"); + printf("\n"); + printf("Options:\n"); + printf(" -o QD image file (default: %s)\n", DEFAULTQDFILE); + printf("\n"); +} + +int main(int argc, char *argv[]) +{ + printf("\nMZQDTool V%s (c) 2002 BKK, 2026 Philip Smart\n\n", VERSION); + + if (argc < 2) { + usage(); + return 1; + } + + /* Parse -o option from any position */ + for (int i = 1; i < argc - 1; i++) { + if (strcmp("-o", argv[i]) == 0) { + strncpy(qdFileName, argv[i + 1], sizeof(qdFileName) - 1); + qdFileName[sizeof(qdFileName) - 1] = '\0'; + /* Remove -o and its argument from argv for command parsing */ + for (int j = i; j < argc - 2; j++) + argv[j] = argv[j + 2]; + argc -= 2; + break; + } + } + + if (strcmp("format", argv[1]) == 0) { + QDFormat(); + } else if (strcmp("dir", argv[1]) == 0) { + QDDir(); + } else if (strcmp("add", argv[1]) == 0) { + if (argc < 3) { + fprintf(stderr, "ERROR: No MZF file specified\n"); + return 2; + } + AddFileToQD(argv[2]); + } else { + fprintf(stderr, "ERROR: Unknown command '%s'\n", argv[1]); + usage(); + return 1; + } + + return 0; +} diff --git a/projects/tzpuPico/tools/MZQD/Makefile b/projects/tzpuPico/tools/MZQD/Makefile new file mode 100644 index 0000000..8ffb0eb --- /dev/null +++ b/projects/tzpuPico/tools/MZQD/Makefile @@ -0,0 +1,19 @@ +# MZQDTool - Sharp MZ-700 Quick Disk image tool +# (c) 2002 BKK, 2026 Philip Smart +# +# Usage: +# make Build MZQDTool +# make clean Remove build artifacts + +CC = gcc +CFLAGS = -Wall -Wno-unused-result -O2 +TARGET = MZQDTool +SRC = MZQDTool.c + +$(TARGET): $(SRC) + $(CC) $(CFLAGS) -o $@ $< + +clean: + rm -f $(TARGET) + +.PHONY: clean diff --git a/projects/tzpuPico/tools/MZQD/side_a/AUTO RUN.mzf b/projects/tzpuPico/tools/MZQD/side_a/AUTO RUN.mzf new file mode 100644 index 0000000..898f4fc Binary files /dev/null and b/projects/tzpuPico/tools/MZQD/side_a/AUTO RUN.mzf differ diff --git a/projects/tzpuPico/tools/MZQD/side_a/BASIC 5Z008A RFS.mzf b/projects/tzpuPico/tools/MZQD/side_a/BASIC 5Z008A RFS.mzf new file mode 100644 index 0000000..12474c2 Binary files /dev/null and b/projects/tzpuPico/tools/MZQD/side_a/BASIC 5Z008A RFS.mzf differ diff --git a/projects/tzpuPico/tools/MZQD/side_a/BASIC 5Z008ADRFS.mzf b/projects/tzpuPico/tools/MZQD/side_a/BASIC 5Z008ADRFS.mzf new file mode 100644 index 0000000..6157655 Binary files /dev/null and b/projects/tzpuPico/tools/MZQD/side_a/BASIC 5Z008ADRFS.mzf differ diff --git a/projects/tzpuPico/tools/MZQD/side_a/BASIC MZ-5Z008 ORIG.mzf b/projects/tzpuPico/tools/MZQD/side_a/BASIC MZ-5Z008 ORIG.mzf new file mode 100644 index 0000000..c9879b8 Binary files /dev/null and b/projects/tzpuPico/tools/MZQD/side_a/BASIC MZ-5Z008 ORIG.mzf differ diff --git a/projects/tzpuPico/tools/MZQD/side_a/BASIC MZ-5Z008.mzf b/projects/tzpuPico/tools/MZQD/side_a/BASIC MZ-5Z008.mzf new file mode 100644 index 0000000..90902da Binary files /dev/null and b/projects/tzpuPico/tools/MZQD/side_a/BASIC MZ-5Z008.mzf differ diff --git a/projects/tzpuPico/tools/MZQD/side_b/DELETE.mzf b/projects/tzpuPico/tools/MZQD/side_b/DELETE.mzf new file mode 100644 index 0000000..5c8c80a Binary files /dev/null and b/projects/tzpuPico/tools/MZQD/side_b/DELETE.mzf differ diff --git a/projects/tzpuPico/tools/MZQD/side_b/QDCOPY.mzf b/projects/tzpuPico/tools/MZQD/side_b/QDCOPY.mzf new file mode 100644 index 0000000..5cfc694 Binary files /dev/null and b/projects/tzpuPico/tools/MZQD/side_b/QDCOPY.mzf differ diff --git a/projects/tzpuPico/tools/MZQD/side_b/TRANS.mzf b/projects/tzpuPico/tools/MZQD/side_b/TRANS.mzf new file mode 100644 index 0000000..c227860 Binary files /dev/null and b/projects/tzpuPico/tools/MZQD/side_b/TRANS.mzf differ diff --git a/projects/tzpuPico/tools/MZQD/side_b_applications/OPENING 800.mzf b/projects/tzpuPico/tools/MZQD/side_b_applications/OPENING 800.mzf new file mode 100644 index 0000000..b3f5377 Binary files /dev/null and b/projects/tzpuPico/tools/MZQD/side_b_applications/OPENING 800.mzf differ diff --git a/projects/tzpuPico/tools/MZQD/side_b_applications/OPENING DATA.mzf b/projects/tzpuPico/tools/MZQD/side_b_applications/OPENING DATA.mzf new file mode 100644 index 0000000..d50aa85 Binary files /dev/null and b/projects/tzpuPico/tools/MZQD/side_b_applications/OPENING DATA.mzf differ