619 lines
21 KiB
C
Executable File
619 lines
21 KiB
C
Executable File
/* =========================================================================
|
|
* MZFDTool — Sharp MZ-700 Floppy Disk image tool
|
|
*
|
|
* Creates and manages raw floppy disk images for the MZ-700 FDC system.
|
|
* Supports format, directory listing, adding/extracting MZF files, and
|
|
* setting a boot program.
|
|
*
|
|
* Original V1.01 (c) 2002 by BKK (DOS BIOS int13h version)
|
|
* Updated V2.00 (c) 2026 Philip Smart — rewritten for Linux with raw
|
|
* image file I/O, proper directory management, MZF conversion,
|
|
* and robust error handling.
|
|
*
|
|
* MZ-700 Floppy Disk format (327,680 bytes raw image):
|
|
*
|
|
* Geometry: 40 cylinders, 2 heads, 16 sectors/track, 256 bytes/sector.
|
|
* Data on disk is INVERTED (XOR 0xFF) due to MZ-700 hardware.
|
|
*
|
|
* Logical sector = (cylinder * 2 + head) * 16 + (phys_sector - 1)
|
|
* Total sectors: 40 * 2 * 16 = 1280
|
|
*
|
|
* Boot sector: Logical sector 0 (Cyl 0, Head 0, Sector 1)
|
|
* Directory: Logical sectors 16-31 (Cyl 0, Head 1, Sectors 1-16)
|
|
* 16 sectors * 8 entries/sector = 128 entries
|
|
* Entry 0 of sector 16 = volume/FAT header
|
|
* Data area: Logical sector 32+ (Cyl 1+)
|
|
*
|
|
* Boot sector (32 bytes used):
|
|
* +00: Type (03 = MZ-700 bootable)
|
|
* +01: Signature "IPLPRO" (6 bytes)
|
|
* +07: Program name (11 bytes, CR-terminated)
|
|
* +12: Load address (2 bytes LE)
|
|
* +14: File size (2 bytes LE)
|
|
* +16: Reserved (8 bytes)
|
|
* +1E: Start data sector (2 bytes LE)
|
|
*
|
|
* Directory entry (32 bytes):
|
|
* +00: FileType (01=OBJ, 02=BTX, 00=deleted, >=80h=header)
|
|
* +01: FileName (17 bytes, CR-terminated, space-padded)
|
|
* +12: LockFlag (1 byte)
|
|
* +13: DummyFlag (1 byte)
|
|
* +14: FileSize (2 bytes LE, in bytes)
|
|
* +16: LoadAddress (2 bytes LE)
|
|
* +18: ExecAddress (2 bytes LE)
|
|
* +1A: Reserved (4 bytes)
|
|
* +1E: SectorAddress (2 bytes LE, logical)
|
|
*
|
|
* Usage:
|
|
* MZFDTool format [-o disk.img] Format empty disk image
|
|
* MZFDTool dir [-o disk.img] List directory
|
|
* MZFDTool add <file.mzf> [-o disk.img] Add MZF file to directory
|
|
* MZFDTool extract <name> [-o disk.img] Extract file to MZF
|
|
* MZFDTool boot <file.mzf> [-o disk.img] Set boot program
|
|
* ========================================================================= */
|
|
|
|
#define VERSION "2.00"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
#include <ctype.h>
|
|
|
|
#define CYLS 40
|
|
#define HEADS 2
|
|
#define SECTORS 16
|
|
#define SECSIZE 256
|
|
#define TOTAL_SECTORS (CYLS * HEADS * SECTORS)
|
|
#define IMGSIZE (TOTAL_SECTORS * SECSIZE)
|
|
#define DIR_FIRST_SEC 16 /* First directory logical sector */
|
|
#define DIR_SECTORS 16 /* Number of directory sectors */
|
|
#define DIR_ENTRIES 8 /* Entries per sector */
|
|
#define DATA_FIRST_SEC 32 /* First data logical sector */
|
|
#define MAXFILETYPES 12
|
|
#define DEFAULTIMAGE "MZ700.img"
|
|
#define CMTHDRSIZE 128 /* MZF/CMT file header size */
|
|
#define BOOT_SIG_TYPE 3 /* MZ-700 boot type */
|
|
#define BOOT_SIG "IPLPRO"
|
|
#define BOOT_SIG_LEN 6
|
|
|
|
#pragma pack(push, 1)
|
|
|
|
/* Boot sector structure (first 32 bytes of logical sector 0) */
|
|
struct BootSector {
|
|
uint8_t type; /* +00: 03 = MZ-700 bootable */
|
|
char signature[6]; /* +01: "IPLPRO" */
|
|
char name[11]; /* +07: Program name (CR-terminated) */
|
|
uint16_t loadAddress; /* +12: Load address */
|
|
uint16_t fileSize; /* +14: File size in bytes */
|
|
uint8_t reserved[8]; /* +16: Reserved */
|
|
uint16_t sectorAddress; /* +1E: Start data sector */
|
|
};
|
|
|
|
/* Directory entry (32 bytes) */
|
|
struct DirEntry {
|
|
uint8_t fileType; /* +00: 01=OBJ, 02=BTX, 00=deleted */
|
|
char fileName[17]; /* +01: CR-terminated, space-padded */
|
|
uint8_t lockFlag; /* +12: Lock flag */
|
|
uint8_t dummyFlag; /* +13: Reserved */
|
|
uint16_t fileSize; /* +14: File size in bytes */
|
|
uint16_t loadAddress; /* +16: Load address */
|
|
uint16_t execAddress; /* +18: Exec address */
|
|
uint8_t dummy[4]; /* +1A: Reserved */
|
|
uint16_t sectorAddress; /* +1E: Start sector (logical) */
|
|
};
|
|
|
|
/* MZF/CMT tape file header (128 bytes) */
|
|
struct CMTHeader {
|
|
uint8_t attribute; /* File type: 01=OBJ, 02=BTX, etc. */
|
|
char name[17]; /* Filename (CR-terminated) */
|
|
uint16_t size; /* Data size */
|
|
uint16_t loadAddress; /* Load address */
|
|
uint16_t execAddress; /* Execution address */
|
|
char comment[104]; /* Comment area */
|
|
};
|
|
|
|
#pragma pack(pop)
|
|
|
|
static const char *FileTypes[MAXFILETYPES] = {
|
|
"???", "OBJ", "BTX", "BSD", "BRD", "RB ",
|
|
"???", "LIB", "???", "???", "SYS", "GR "
|
|
};
|
|
|
|
static uint8_t diskImage[IMGSIZE];
|
|
static char imgFileName[256] = DEFAULTIMAGE;
|
|
|
|
/* ------- Sector I/O helpers ------- */
|
|
|
|
/* Convert logical sector to byte offset in raw image */
|
|
static int sec_offset(int logical_sec)
|
|
{
|
|
/* Logical sector mapping:
|
|
* phys_sector = logical % 16 + 1 (but stored 0-based in image)
|
|
* head = (logical / 16) % 2
|
|
* cylinder = logical / 16 / 2
|
|
* Raw image is sequential: cyl0/head0/sec1..16, cyl0/head1/sec1..16, ... */
|
|
return logical_sec * SECSIZE;
|
|
}
|
|
|
|
/* Invert a buffer (XOR 0xFF) — MZ-700 data bus inversion */
|
|
static void invert_buf(uint8_t *buf, int len)
|
|
{
|
|
for (int i = 0; i < len; i++)
|
|
buf[i] ^= 0xFF;
|
|
}
|
|
|
|
/* Read a logical sector from image into buffer (un-inverted) */
|
|
static void read_sector(int logical_sec, uint8_t *buf)
|
|
{
|
|
memcpy(buf, &diskImage[sec_offset(logical_sec)], SECSIZE);
|
|
invert_buf(buf, SECSIZE);
|
|
}
|
|
|
|
/* Write a buffer to a logical sector in image (inverts before writing) */
|
|
static void write_sector(int logical_sec, const uint8_t *buf)
|
|
{
|
|
memcpy(&diskImage[sec_offset(logical_sec)], buf, SECSIZE);
|
|
invert_buf(&diskImage[sec_offset(logical_sec)], SECSIZE);
|
|
}
|
|
|
|
/* Load disk image from file */
|
|
static int load_image(void)
|
|
{
|
|
FILE *f = fopen(imgFileName, "rb");
|
|
if (!f) {
|
|
fprintf(stderr, "ERROR: Cannot open '%s'\n", imgFileName);
|
|
return 0;
|
|
}
|
|
size_t n = fread(diskImage, 1, IMGSIZE, f);
|
|
fclose(f);
|
|
if (n != IMGSIZE) {
|
|
fprintf(stderr, "ERROR: '%s' is not a valid disk image (%zu bytes, expected %d)\n",
|
|
imgFileName, n, IMGSIZE);
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* Save disk image to file */
|
|
static int save_image(void)
|
|
{
|
|
FILE *f = fopen(imgFileName, "wb");
|
|
if (!f) {
|
|
fprintf(stderr, "ERROR: Cannot write '%s'\n", imgFileName);
|
|
return 0;
|
|
}
|
|
fwrite(diskImage, IMGSIZE, 1, f);
|
|
fclose(f);
|
|
return 1;
|
|
}
|
|
|
|
/* Format a filename for display: stop at CR, null-terminate */
|
|
static void format_name(char *dst, const char *src, int maxlen)
|
|
{
|
|
memcpy(dst, src, maxlen);
|
|
dst[maxlen] = '\0';
|
|
for (int i = 0; i < maxlen; i++) {
|
|
if (dst[i] == '\r' || dst[i] == '\0') { dst[i] = '\0'; break; }
|
|
}
|
|
}
|
|
|
|
/* ------- Commands ------- */
|
|
|
|
/* Format: create an empty formatted disk image */
|
|
static void cmd_format(void)
|
|
{
|
|
uint8_t sec[SECSIZE];
|
|
|
|
printf("Formatting '%s' (%d bytes, %d sectors)\n", imgFileName, IMGSIZE, TOTAL_SECTORS);
|
|
|
|
/* Fill entire image with 0xFF (which inverts to 0x00 = empty) */
|
|
memset(diskImage, 0xFF, IMGSIZE);
|
|
|
|
/* Write volume header in directory sector 16, entry 0 */
|
|
memset(sec, 0, SECSIZE);
|
|
sec[0] = 0x81; /* Volume header marker */
|
|
sec[1] = 'O'; /* Volume type */
|
|
/* +14: capacity hint = 100 (not used by RFS) */
|
|
sec[0x14] = 100;
|
|
/* +1E: next free sector (first data sector) */
|
|
sec[0x1E] = DATA_FIRST_SEC & 0xFF;
|
|
sec[0x1F] = DATA_FIRST_SEC >> 8;
|
|
write_sector(DIR_FIRST_SEC, sec);
|
|
|
|
if (!save_image()) exit(1);
|
|
puts("Done.");
|
|
}
|
|
|
|
/* Dir: list directory contents */
|
|
static void cmd_dir(void)
|
|
{
|
|
uint8_t sec[SECSIZE];
|
|
char name[18];
|
|
int files = 0;
|
|
|
|
if (!load_image()) exit(1);
|
|
|
|
/* Check boot sector */
|
|
read_sector(0, sec);
|
|
struct BootSector *boot = (struct BootSector *)sec;
|
|
if (boot->type == BOOT_SIG_TYPE && memcmp(boot->signature, BOOT_SIG, BOOT_SIG_LEN) == 0) {
|
|
format_name(name, boot->name, 11);
|
|
printf("\nBoot program: %-17s Size=%u Load=0x%04X Sector=%u\n",
|
|
name, boot->fileSize, boot->loadAddress, boot->sectorAddress);
|
|
} else {
|
|
printf("\nDisk is not bootable.\n");
|
|
}
|
|
|
|
printf("\nDirectory of '%s':\n\n", imgFileName);
|
|
printf(" %-17s %-3s %5s %s %4s %4s %6s (C H S)\n",
|
|
"Name", "Typ", "Size", "L", "Load", "Exec", "Sector");
|
|
printf(" %-17s %-3s %5s %s %4s %4s %6s %s\n",
|
|
"-----------------", "---", "-----", "-", "----", "----", "------", "-------");
|
|
|
|
for (int ds = 0; ds < DIR_SECTORS; ds++) {
|
|
read_sector(DIR_FIRST_SEC + ds, sec);
|
|
|
|
for (int e = 0; e < DIR_ENTRIES; e++) {
|
|
/* Skip entry 0 of first directory sector (volume header) */
|
|
if (ds == 0 && e == 0) continue;
|
|
|
|
struct DirEntry *ent = (struct DirEntry *)&sec[e * sizeof(struct DirEntry)];
|
|
if (ent->fileType == 0x00 || ent->fileType >= 0x80)
|
|
continue;
|
|
|
|
uint8_t ft = ent->fileType;
|
|
if (ft >= MAXFILETYPES) ft = 0;
|
|
format_name(name, ent->fileName, 17);
|
|
|
|
int ls = ent->sectorAddress;
|
|
int ps = ls % 16 + 1;
|
|
int hd = (ls / 16) % 2;
|
|
int cy = ls / 16 / 2;
|
|
|
|
printf(" %-17s %s %5u %c %04X %04X %5u (%2d %d %2d)\n",
|
|
name, FileTypes[ft], ent->fileSize,
|
|
ent->lockFlag ? 'X' : ' ',
|
|
ent->loadAddress, ent->execAddress,
|
|
ent->sectorAddress, cy, hd, ps);
|
|
files++;
|
|
}
|
|
}
|
|
|
|
/* Calculate free space */
|
|
int highSec = DATA_FIRST_SEC;
|
|
for (int ds = 0; ds < DIR_SECTORS; ds++) {
|
|
read_sector(DIR_FIRST_SEC + ds, sec);
|
|
for (int e = 0; e < DIR_ENTRIES; e++) {
|
|
struct DirEntry *ent = (struct DirEntry *)&sec[e * sizeof(struct DirEntry)];
|
|
if (ent->fileType > 0 && ent->fileType < 0x80 && ent->sectorAddress > 0) {
|
|
int endSec = ent->sectorAddress + (ent->fileSize + SECSIZE - 1) / SECSIZE;
|
|
if (endSec > highSec) highSec = endSec;
|
|
}
|
|
}
|
|
}
|
|
printf("\n%d file(s), %d sectors used, %d sectors free\n",
|
|
files, highSec, TOTAL_SECTORS - highSec);
|
|
}
|
|
|
|
/* Add: add an MZF file to the disk directory */
|
|
static void cmd_add(const char *mzfFileName)
|
|
{
|
|
FILE *mzfFile;
|
|
uint8_t sec[SECSIZE];
|
|
struct CMTHeader cmtHdr;
|
|
char name[18];
|
|
|
|
if (!load_image()) exit(1);
|
|
|
|
/* 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 mzfSize = ftell(mzfFile);
|
|
fseek(mzfFile, 0, SEEK_SET);
|
|
|
|
if (mzfSize < CMTHDRSIZE) {
|
|
fprintf(stderr, "ERROR: '%s' too small for MZF format (%ld bytes)\n", mzfFileName, mzfSize);
|
|
fclose(mzfFile);
|
|
exit(1);
|
|
}
|
|
fread(&cmtHdr, CMTHDRSIZE, 1, mzfFile);
|
|
uint16_t dataSize = cmtHdr.size;
|
|
if (dataSize == 0) dataSize = (uint16_t)(mzfSize - CMTHDRSIZE);
|
|
|
|
/* Find free directory entry and highest used sector */
|
|
int freeDirSec = -1, freeDirIdx = -1;
|
|
int highSec = DATA_FIRST_SEC;
|
|
|
|
for (int ds = 0; ds < DIR_SECTORS; ds++) {
|
|
read_sector(DIR_FIRST_SEC + ds, sec);
|
|
for (int e = 0; e < DIR_ENTRIES; e++) {
|
|
if (ds == 0 && e == 0) continue; /* Skip volume header */
|
|
struct DirEntry *ent = (struct DirEntry *)&sec[e * sizeof(struct DirEntry)];
|
|
|
|
if (ent->fileType == 0x00 && freeDirSec < 0) {
|
|
freeDirSec = ds;
|
|
freeDirIdx = e;
|
|
}
|
|
if (ent->fileType > 0 && ent->fileType < 0x80 && ent->sectorAddress > 0) {
|
|
int endSec = ent->sectorAddress + (ent->fileSize + SECSIZE - 1) / SECSIZE;
|
|
if (endSec > highSec) highSec = endSec;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (freeDirSec < 0) {
|
|
fprintf(stderr, "ERROR: Directory full\n");
|
|
fclose(mzfFile);
|
|
exit(1);
|
|
}
|
|
|
|
int dataSectors = (dataSize + SECSIZE - 1) / SECSIZE;
|
|
if (highSec + dataSectors > TOTAL_SECTORS) {
|
|
fprintf(stderr, "ERROR: Not enough space (need %d sectors, have %d)\n",
|
|
dataSectors, TOTAL_SECTORS - highSec);
|
|
fclose(mzfFile);
|
|
exit(1);
|
|
}
|
|
|
|
/* Write file data sectors */
|
|
for (int s = 0; s < dataSectors; s++) {
|
|
memset(sec, 0, SECSIZE);
|
|
fread(sec, 1, SECSIZE, mzfFile);
|
|
write_sector(highSec + s, sec);
|
|
}
|
|
fclose(mzfFile);
|
|
|
|
/* Update directory entry */
|
|
read_sector(DIR_FIRST_SEC + freeDirSec, sec);
|
|
struct DirEntry *ent = (struct DirEntry *)&sec[freeDirIdx * sizeof(struct DirEntry)];
|
|
memset(ent, 0, sizeof(struct DirEntry));
|
|
|
|
ent->fileType = cmtHdr.attribute;
|
|
if (ent->fileType == 0x05) ent->fileType = 0x02; /* CMT type 05 → FD type 02 (BTX) */
|
|
memcpy(ent->fileName, cmtHdr.name, 17);
|
|
ent->fileSize = dataSize;
|
|
ent->loadAddress = cmtHdr.loadAddress;
|
|
ent->execAddress = cmtHdr.execAddress;
|
|
ent->sectorAddress = highSec;
|
|
write_sector(DIR_FIRST_SEC + freeDirSec, sec);
|
|
|
|
format_name(name, cmtHdr.name, 17);
|
|
printf(" Added: \"%s\" type=%s size=%u load=0x%04X exec=0x%04X sector=%d\n",
|
|
name, FileTypes[ent->fileType < MAXFILETYPES ? ent->fileType : 0],
|
|
dataSize, cmtHdr.loadAddress, cmtHdr.execAddress, highSec);
|
|
printf(" %d sectors free\n", TOTAL_SECTORS - highSec - dataSectors);
|
|
|
|
if (!save_image()) exit(1);
|
|
}
|
|
|
|
/* Extract: extract a file from disk to MZF format */
|
|
static void cmd_extract(const char *fileName)
|
|
{
|
|
uint8_t sec[SECSIZE];
|
|
char entName[18], outName[256];
|
|
|
|
if (!load_image()) exit(1);
|
|
|
|
/* Search directory for matching filename */
|
|
static struct DirEntry foundEntry;
|
|
int foundIt = 0;
|
|
|
|
for (int ds = 0; ds < DIR_SECTORS && !foundIt; ds++) {
|
|
read_sector(DIR_FIRST_SEC + ds, sec);
|
|
for (int e = 0; e < DIR_ENTRIES && !foundIt; e++) {
|
|
if (ds == 0 && e == 0) continue;
|
|
struct DirEntry *ent = (struct DirEntry *)&sec[e * sizeof(struct DirEntry)];
|
|
if (ent->fileType > 0 && ent->fileType < 0x80) {
|
|
format_name(entName, ent->fileName, 17);
|
|
if (strcmp(fileName, entName) == 0) {
|
|
memcpy(&foundEntry, ent, sizeof(foundEntry));
|
|
foundIt = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!foundIt) {
|
|
/* Also check boot sector */
|
|
read_sector(0, sec);
|
|
struct BootSector *boot = (struct BootSector *)sec;
|
|
if (boot->type == BOOT_SIG_TYPE && memcmp(boot->signature, BOOT_SIG, BOOT_SIG_LEN) == 0) {
|
|
format_name(entName, boot->name, 11);
|
|
if (strcmp(fileName, entName) == 0) {
|
|
memset(&foundEntry, 0, sizeof(foundEntry));
|
|
foundEntry.fileType = 0x01;
|
|
memcpy(foundEntry.fileName, boot->name, 11);
|
|
foundEntry.fileSize = boot->fileSize;
|
|
foundEntry.loadAddress = boot->loadAddress;
|
|
foundEntry.sectorAddress = boot->sectorAddress;
|
|
foundIt = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!foundIt) {
|
|
fprintf(stderr, "ERROR: '%s' not found on disk\n", fileName);
|
|
exit(1);
|
|
}
|
|
|
|
struct DirEntry *found = &foundEntry;
|
|
|
|
/* Build output filename */
|
|
snprintf(outName, sizeof(outName), "%s.mzf", fileName);
|
|
for (char *p = outName; *p; p++) {
|
|
if (*p == ' ') *p = '_';
|
|
}
|
|
|
|
/* Write MZF file */
|
|
FILE *out = fopen(outName, "wb");
|
|
if (!out) {
|
|
fprintf(stderr, "ERROR: Cannot create '%s'\n", outName);
|
|
exit(1);
|
|
}
|
|
|
|
struct CMTHeader hdr;
|
|
memset(&hdr, 0, sizeof(hdr));
|
|
hdr.attribute = (found->fileType == 0x02) ? 0x05 : found->fileType;
|
|
memcpy(hdr.name, found->fileName, 17);
|
|
hdr.size = found->fileSize;
|
|
hdr.loadAddress = found->loadAddress;
|
|
hdr.execAddress = found->execAddress;
|
|
snprintf(hdr.comment, sizeof(hdr.comment), "Extracted by MZFDTool V%s", VERSION);
|
|
fwrite(&hdr, sizeof(hdr), 1, out);
|
|
|
|
/* Read and write data sectors */
|
|
uint16_t remaining = found->fileSize;
|
|
int curSec = found->sectorAddress;
|
|
while (remaining > 0) {
|
|
read_sector(curSec, sec);
|
|
uint16_t chunk = (remaining > SECSIZE) ? SECSIZE : remaining;
|
|
fwrite(sec, 1, chunk, out);
|
|
remaining -= chunk;
|
|
curSec++;
|
|
}
|
|
|
|
fclose(out);
|
|
format_name(entName, found->fileName, 17);
|
|
printf(" Extracted: \"%s\" → %s (%u bytes)\n", entName, outName, found->fileSize);
|
|
}
|
|
|
|
/* Boot: set boot program from MZF file */
|
|
static void cmd_boot(const char *mzfFileName)
|
|
{
|
|
FILE *mzfFile;
|
|
uint8_t sec[SECSIZE];
|
|
struct CMTHeader cmtHdr;
|
|
char name[18];
|
|
|
|
if (!load_image()) exit(1);
|
|
|
|
mzfFile = fopen(mzfFileName, "rb");
|
|
if (!mzfFile) {
|
|
fprintf(stderr, "ERROR: Cannot open '%s'\n", mzfFileName);
|
|
exit(1);
|
|
}
|
|
fseek(mzfFile, 0, SEEK_END);
|
|
long mzfSize = ftell(mzfFile);
|
|
fseek(mzfFile, 0, SEEK_SET);
|
|
|
|
if (mzfSize < CMTHDRSIZE) {
|
|
fprintf(stderr, "ERROR: '%s' too small for MZF format\n", mzfFileName);
|
|
fclose(mzfFile);
|
|
exit(1);
|
|
}
|
|
fread(&cmtHdr, CMTHDRSIZE, 1, mzfFile);
|
|
uint16_t dataSize = cmtHdr.size;
|
|
if (dataSize == 0) dataSize = (uint16_t)(mzfSize - CMTHDRSIZE);
|
|
|
|
/* Find space for boot data — use sectors 1-15 (logical), before directory */
|
|
int dataSectors = (dataSize + SECSIZE - 1) / SECSIZE;
|
|
int bootDataStart = 1; /* Sector 1 (sector 0 is the boot sector itself) */
|
|
if (dataSectors > 15) {
|
|
fprintf(stderr, "ERROR: Boot program too large (%d sectors, max 15)\n", dataSectors);
|
|
fclose(mzfFile);
|
|
exit(1);
|
|
}
|
|
|
|
/* Write boot data to sectors 1-15 */
|
|
for (int s = 0; s < dataSectors; s++) {
|
|
memset(sec, 0, SECSIZE);
|
|
fread(sec, 1, SECSIZE, mzfFile);
|
|
write_sector(bootDataStart + s, sec);
|
|
}
|
|
fclose(mzfFile);
|
|
|
|
/* Write boot sector (sector 0) */
|
|
memset(sec, 0, SECSIZE);
|
|
struct BootSector *boot = (struct BootSector *)sec;
|
|
boot->type = BOOT_SIG_TYPE;
|
|
memcpy(boot->signature, BOOT_SIG, BOOT_SIG_LEN);
|
|
format_name(name, cmtHdr.name, 11);
|
|
memcpy(boot->name, cmtHdr.name, 11);
|
|
boot->loadAddress = cmtHdr.loadAddress;
|
|
boot->fileSize = dataSize;
|
|
boot->sectorAddress = bootDataStart;
|
|
write_sector(0, sec);
|
|
|
|
format_name(name, cmtHdr.name, 11);
|
|
printf(" Boot set: \"%s\" size=%u load=0x%04X exec=0x%04X sector=%d\n",
|
|
name, dataSize, cmtHdr.loadAddress, cmtHdr.execAddress, bootDataStart);
|
|
|
|
if (!save_image()) exit(1);
|
|
}
|
|
|
|
/* ------- Usage and main ------- */
|
|
|
|
static void usage(void)
|
|
{
|
|
printf("Usage:\n");
|
|
printf(" MZFDTool format [-o disk.img] Format empty disk image\n");
|
|
printf(" MZFDTool dir [-o disk.img] List directory\n");
|
|
printf(" MZFDTool add <file.mzf> [-o disk.img] Add MZF file to disk\n");
|
|
printf(" MZFDTool extract <name> [-o disk.img] Extract file to MZF\n");
|
|
printf(" MZFDTool boot <file.mzf> [-o disk.img] Set boot program\n");
|
|
printf("\n");
|
|
printf("Options:\n");
|
|
printf(" -o <filename> Disk image file (default: %s)\n", DEFAULTIMAGE);
|
|
printf("\n");
|
|
printf("Disk geometry: %d cyls, %d heads, %d sectors, %d bytes/sector = %d bytes\n",
|
|
CYLS, HEADS, SECTORS, SECSIZE, IMGSIZE);
|
|
printf("\n");
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
printf("\nMZFDTool 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(imgFileName, argv[i + 1], sizeof(imgFileName) - 1);
|
|
imgFileName[sizeof(imgFileName) - 1] = '\0';
|
|
for (int j = i; j < argc - 2; j++)
|
|
argv[j] = argv[j + 2];
|
|
argc -= 2;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (strcmp("format", argv[1]) == 0) {
|
|
cmd_format();
|
|
} else if (strcmp("dir", argv[1]) == 0) {
|
|
cmd_dir();
|
|
} else if (strcmp("add", argv[1]) == 0) {
|
|
if (argc < 3) {
|
|
fprintf(stderr, "ERROR: No MZF file specified\n");
|
|
return 2;
|
|
}
|
|
cmd_add(argv[2]);
|
|
} else if (strcmp("extract", argv[1]) == 0) {
|
|
if (argc < 3) {
|
|
fprintf(stderr, "ERROR: No filename specified\n");
|
|
return 2;
|
|
}
|
|
cmd_extract(argv[2]);
|
|
} else if (strcmp("boot", argv[1]) == 0) {
|
|
if (argc < 3) {
|
|
fprintf(stderr, "ERROR: No MZF file specified\n");
|
|
return 2;
|
|
}
|
|
cmd_boot(argv[2]);
|
|
} else {
|
|
fprintf(stderr, "ERROR: Unknown command '%s'\n", argv[1]);
|
|
usage();
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|