Files
RFS/tools/MZQD/MZQDTool.c
2026-04-01 17:16:00 +01:00

367 lines
11 KiB
C
Executable File

/* =========================================================================
* 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;
}