Added MZQD tool to create QD files for Sharp MZ Series

This commit is contained in:
Philip Smart
2026-04-01 16:42:12 +01:00
parent 113b836c91
commit c0b9a6a5af
15 changed files with 385 additions and 0 deletions

Binary file not shown.

Binary file not shown.

BIN
projects/tzpuPico/tools/MZQD/MZQDTool vendored Executable file

Binary file not shown.

View File

@@ -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 <file.mzf> [-o disk.qd]
* ========================================================================= */
#define VERSION "1.00"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#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 <file.mzf> [-o disk.qd] Add MZF file to QD\n");
printf("\n");
printf("Options:\n");
printf(" -o <filename> 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;
}

19
projects/tzpuPico/tools/MZQD/Makefile vendored Normal file
View File

@@ -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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.