VolksForth/8086/pc-baremetal/bootdisk/mkimg144.c
2021-04-12 20:27:23 +02:00

613 lines
14 KiB
C

#include <limits.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <time.h>
typedef unsigned char uchar, uint8;
typedef unsigned short uint16;
#ifndef __SMALLER_C__
#if UINT_MAX >= 0xFFFFFFFF
typedef unsigned uint32;
#else
typedef unsigned long uint32;
#endif
#else
typedef unsigned long uint32;
#endif
typedef unsigned uint;
typedef unsigned long ulong;
#ifndef __SMALLER_C__
#define C_ASSERT(expr) extern char CAssertExtern[(expr)?1:-1]
C_ASSERT(CHAR_BIT == 8);
C_ASSERT(sizeof(uint16) == 2);
C_ASSERT(sizeof(uint32) == 4);
#endif
#pragma pack (push, 1)
typedef struct tFATBPB1
{
uint16 BytesPerSector;
uint8 SectorsPerCluster;
uint16 ReservedSectorsCount;
uint8 NumberOfFATs;
uint16 RootEntriesCount;
uint16 TotalSectorsCount16;
uint8 MediaType;
uint16 SectorsPerFAT1x;
uint16 SectorsPerTrack;
uint16 HeadsPerCylinder;
uint32 HiddenSectorsCount;
uint32 TotalSectorsCount32;
} tFATBPB1;
typedef union tFATBPB2
{
struct
{
uint8 DriveNumber;
uint8 reserved1;
uint8 ExtendedBootSignature;
uint32 VolumeSerialNumber;
char VolumeLabel[11];
char FileSystemName[8];
uchar aBootCode1x[0x1C];
} FAT1x;
struct
{
uint32 SectorsPerFAT32;
uint16 ExtendedFlags;
uint16 FSVersion;
uint32 RootDirectoryClusterNo;
uint16 FSInfoSectorNo;
uint16 BackupBootSectorNo;
uint8 reserved[12];
uint8 DriveNumber;
uint8 reserved1;
uint8 ExtendedBootSignature;
uint32 VolumeSerialNumber;
char VolumeLabel[11];
char FileSystemName[8];
} FAT32;
} tFATBPB2;
typedef struct tFATBPB
{
tFATBPB1 BPB1;
tFATBPB2 BPB2;
} tFATBPB;
typedef struct tFATBootSector
{
uchar aJump[3];
char OEMName[8];
tFATBPB BPB;
uchar aBootCode32[0x1A4];
uint16 Signature0xAA55;
} tFATBootSector;
typedef enum tFATDirEntryAttribute
{
dea_READ_ONLY = 0x01,
dea_HIDDEN = 0x02,
dea_SYSTEM = 0x04,
dea_VOLUME_ID = 0x08,
dea_DIRECTORY = 0x10,
dea_ARCHIVE = 0x20,
dea_LONG_NAME = dea_READ_ONLY|dea_HIDDEN|dea_SYSTEM|dea_VOLUME_ID
} tFATDirEntryAttribute;
typedef struct tFATDirectoryEntry
{
char Name[8];
char Extension[3];
uint8 Attribute;
uint8 WinNTreserved;
uint8 CreationTimeSecTenths;
uint16 CreationTime2Secs;
uint16 CreationDate;
uint16 LastAccessDate;
uint16 FirstClusterHiWord;
uint16 LastWriteTime;
uint16 LastWriteDate;
uint16 FirstClusterLoWord;
uint32 Size;
} tFATDirectoryEntry;
#define DELETED_DIR_ENTRY_MARKER 0xE5
#pragma pack (pop)
#ifndef __SMALLER_C_32__
C_ASSERT(sizeof(tFATBootSector) == 512);
C_ASSERT(sizeof(tFATDirectoryEntry) == 32);
#endif
#define FBUF_SIZE 1024
char* BootSectName;
char* OutName = "floppy.img";
int UniqueSerial;
FILE* fout;
tFATBootSector BootSector;
uint32 Fat1Lba;
uint32 SectorsPerFat;
uint32 Fats;
uint32 RootDirLba;
uint32 DirEntriesPerSector;
uint32 RootDirEntries;
uint32 RootDirSectors;
uint32 Cluster2Lba;
uint32 SectorsPerCluster;
uint32 ClusterSize;
uint32 DataSectors;
uint32 Clusters;
uint8 FatSector[512];
uint32 Cluster;
tFATDirectoryEntry RootDirSector[512 / sizeof(tFATDirectoryEntry)];
uint32 RootDirEntryIdx;
void error(char* format, ...)
{
#ifndef __SMALLER_C__
va_list vl;
va_start(vl, format);
#else
void* vl = &format + 1;
#endif
if (fout)
fclose(fout);
remove(OutName);
puts("");
vprintf(format, vl);
#ifndef __SMALLER_C__
va_end(vl);
#endif
exit(EXIT_FAILURE);
}
FILE* Fopen(const char* filename, const char* mode)
{
FILE* stream = fopen(filename, mode);
if (!stream)
error("Can't open/create file \"%s\"\n", filename);
return stream;
}
void Fclose(FILE* stream)
{
if (fclose(stream))
error("Can't close a file\n");
}
void Fseek(FILE* stream, long offset, int whence)
{
int r = fseek(stream, offset, whence);
if (r)
error("Can't seek a file\n");
}
void Fread(void* ptr, size_t size, FILE* stream)
{
size_t r = fread(ptr, 1, size, stream);
if (r != size)
error("Can't read a file\n");
}
void Fwrite(const void* ptr, size_t size, FILE* stream)
{
size_t r = fwrite(ptr, 1, size, stream);
if (r != size)
error("Can't write a file\n");
}
void FillWithByte(unsigned char byte, unsigned long size, FILE* stream)
{
static unsigned char buf[FBUF_SIZE];
memset(buf, byte, FBUF_SIZE);
while (size)
{
unsigned long csz = size;
if (csz > FBUF_SIZE)
csz = FBUF_SIZE;
Fwrite(buf, csz, stream);
size -= csz;
}
}
// Determines binary file size portably (when stat()/fstat() aren't available)
long fsize(FILE* binaryStream)
{
long ofs, ofs2;
int result;
if (fseek(binaryStream, 0, SEEK_SET) != 0 ||
fgetc(binaryStream) == EOF)
return 0;
ofs = 1;
while ((result = fseek(binaryStream, ofs, SEEK_SET)) == 0 &&
(result = (fgetc(binaryStream) == EOF)) == 0 &&
ofs <= LONG_MAX / 4 + 1)
ofs *= 2;
// If the last seek failed, back up to the last successfully seekable offset
if (result != 0)
ofs /= 2;
for (ofs2 = ofs / 2; ofs2 != 0; ofs2 /= 2)
if (fseek(binaryStream, ofs + ofs2, SEEK_SET) == 0 &&
fgetc(binaryStream) != EOF)
ofs += ofs2;
// Return -1 for files longer than LONG_MAX
if (ofs == LONG_MAX)
return -1;
return ofs + 1;
}
void FlushFatSector(void)
{
uint32 ofs = (Cluster * 3 / 2) & 511;
uint32 i;
if (ofs == 0 && (Cluster & 1) == 0)
return;
for (i = 0; i < Fats; i++)
{
uint32 ofs = Fat1Lba + i * SectorsPerFat;
ofs += (Cluster * 3 / 2) / 512;
Fseek(fout, ofs * 512, SEEK_SET);
Fwrite(FatSector, sizeof FatSector, fout);
}
memset(FatSector, 0, sizeof FatSector);
}
void ChainCluster(uint32 nextCluster)
{
uint32 ofs = (Cluster * 3 / 2) & 511;
if (Cluster & 1)
FatSector[ofs] |= nextCluster << 4;
else
FatSector[ofs] = nextCluster;
if (ofs == 511)
FlushFatSector();
ofs = (ofs + 1) & 511;
if (Cluster & 1)
FatSector[ofs] = nextCluster >> 4;
else
FatSector[ofs] = (nextCluster >> 8) & 0xF;
if (ofs == 511 && (Cluster & 1))
FlushFatSector();
Cluster++;
}
void FlushRootDirSector(void)
{
uint32 ofs;
if (RootDirEntryIdx % DirEntriesPerSector == 0)
return;
ofs = RootDirLba + RootDirEntryIdx / DirEntriesPerSector;
Fseek(fout, ofs * 512, SEEK_SET);
Fwrite(RootDirSector, sizeof RootDirSector, fout);
}
void AddRootDirEntry(tFATDirectoryEntry* de)
{
RootDirSector[RootDirEntryIdx % DirEntriesPerSector] = *de;
if ((RootDirEntryIdx + 1) % DirEntriesPerSector == 0)
FlushRootDirSector();
RootDirEntryIdx++;
}
void Init(void)
{
if (BootSectName)
{
FILE* fsect = Fopen(BootSectName, "rb");
Fread(&BootSector, sizeof BootSector, fsect);
Fclose(fsect);
}
else
{
memcpy(BootSector.OEMName, "BootProg", 8);
memcpy(BootSector.BPB.BPB2.FAT1x.VolumeLabel, "NO NAME ", 11);
memcpy(BootSector.BPB.BPB2.FAT1x.FileSystemName, "FAT12 ", 8);
BootSector.aJump[0] = 0xEB; // jmp short $+0x3E
BootSector.aJump[1] = 0x3C;
BootSector.aJump[2] = 0x90; // nop
// TBD??? replace the below with code to print an error message like "Not a system/bootable disk"?
BootSector.BPB.BPB2.FAT1x.aBootCode1x[0] = 0xF4; // hlt
BootSector.BPB.BPB2.FAT1x.aBootCode1x[1] = 0xEB; // jmp short $-1
BootSector.BPB.BPB2.FAT1x.aBootCode1x[2] = 0xFD;
}
fout = Fopen(OutName, "wb");
BootSector.BPB.BPB1.BytesPerSector = 512; // note, we're normally assuming 512 bytes per sector everywhere
BootSector.BPB.BPB1.SectorsPerCluster = 1;
BootSector.BPB.BPB1.ReservedSectorsCount = 1; // includes the boot sector
BootSector.BPB.BPB1.NumberOfFATs = 2;
BootSector.BPB.BPB1.RootEntriesCount = 224; // must be a multiple of 16 (16 32-byte entries in 512-byte sector)
BootSector.BPB.BPB1.TotalSectorsCount16 = 2880;
BootSector.BPB.BPB1.MediaType = 0xF0;
BootSector.BPB.BPB1.SectorsPerFAT1x = 9;
BootSector.BPB.BPB1.SectorsPerTrack = 18;
BootSector.BPB.BPB1.HeadsPerCylinder = 2;
BootSector.BPB.BPB1.HiddenSectorsCount = 0;
BootSector.BPB.BPB1.TotalSectorsCount32 = 0;
BootSector.BPB.BPB2.FAT1x.DriveNumber = 0;
BootSector.BPB.BPB2.FAT1x.reserved1 = 0;
BootSector.BPB.BPB2.FAT1x.ExtendedBootSignature = 0x29;
BootSector.BPB.BPB2.FAT1x.VolumeSerialNumber = 0x11223344;
if (UniqueSerial)
BootSector.BPB.BPB2.FAT1x.VolumeSerialNumber = time(NULL);
BootSector.Signature0xAA55 = 0xAA55;
// Write the boot sector
Fwrite(&BootSector, sizeof BootSector, fout);
// Zero out the rest of the image
FillWithByte(0, (BootSector.BPB.BPB1.TotalSectorsCount16 - 1) * 512UL, fout);
// FAT12's first two entries need special initialization
ChainCluster(0xF00 | BootSector.BPB.BPB1.MediaType);
ChainCluster(0xFFF);
// Helper variables
Fat1Lba = BootSector.BPB.BPB1.ReservedSectorsCount;
SectorsPerFat = BootSector.BPB.BPB1.SectorsPerFAT1x;
Fats = BootSector.BPB.BPB1.NumberOfFATs;
RootDirLba = Fat1Lba + SectorsPerFat * Fats;
DirEntriesPerSector = 512 / sizeof(tFATDirectoryEntry);
RootDirEntries = BootSector.BPB.BPB1.RootEntriesCount;
RootDirSectors = (RootDirEntries * sizeof(tFATDirectoryEntry) + 511) / 512;
Cluster2Lba = RootDirLba + RootDirSectors;
SectorsPerCluster = BootSector.BPB.BPB1.SectorsPerCluster;
ClusterSize = SectorsPerCluster * 512;
DataSectors = BootSector.BPB.BPB1.TotalSectorsCount16 -
BootSector.BPB.BPB1.ReservedSectorsCount - SectorsPerFat * Fats - RootDirSectors;
Clusters = DataSectors / SectorsPerCluster;
}
void Done(void)
{
FlushFatSector();
FlushRootDirSector();
Fclose(fout);
}
void NameTo8Dot3Name(const char* in, char out[8 + 3])
{
static const char aInvalid8Dot3NameChars[] = "\"*+,./:;<=>?[\\]|";
int i, j;
int namelen = 0, dots = 0, extlen = 0;
memset(out, ' ', 8 + 3);
if (*in == '\0' || *in == '.')
goto lerr;
for (j = i = 0; in[i]; i++)
{
int c = (unsigned char)in[i];
if (i >= 12) // at most 12 input chars can fit into an 8.3 name
goto lerr;
if (i == 0 && c == 0xE5)
{
// 0xE5 in the first character of the name is a marker for deleted files,
// it needs to be translated to 0x05
c = 0x05;
}
else if (c == '.')
{
if (dots++) // at most one dot allowed
goto lerr;
j = 8; // now writing extension
continue;
}
if (c <= 0x20 || strchr(aInvalid8Dot3NameChars, c) != NULL)
goto lerr;
if (dots)
{
if (++extlen > 3) // at most 3 chars in extension
goto lerr;
}
else
{
if (++namelen > 8) // at most 8 chars in name
goto lerr;
}
if (c >= 'a' && c <= 'z')
c -= 'a' - 'A';
out[j++] = c;
}
// TBD??? error out on the following reserved names: "COM1"-"COM9", "CON", "LPT1"-"LPT9", "NUL", "PRN"?
return;
lerr:
error("Can't convert \"%s\" to an 8.3 DOS name\n", in);
}
void AddFile(char* fname)
{
char* pslash = strrchr(fname, '/');
char* pbackslash = strrchr(fname, '\\');
char* pname;
char name8_3[8 + 3];
FILE* f;
long size;
tFATDirectoryEntry de;
uint32 ofs;
// First, find where the path ends in the file name, if any
// In DOS/Windows paths can contain either '\\' or '/' as a separator between directories,
// choose the right-most
if (pslash && pbackslash)
{
if (pslash < pbackslash)
pslash = pbackslash;
}
else if (!pslash)
{
pslash = pbackslash;
}
// If there's no slash, it could be "c:file"
if (!pslash && ((*fname >= 'A' && *fname <= 'Z') || (*fname >= 'a' && *fname <= 'z')) && fname[1] == ':')
pslash = fname + 1;
pname = pslash ? pslash + 1 : fname;
// Convert the name to 8.3
NameTo8Dot3Name(pname, name8_3);
// TBD!!! error out on duplicate files/names
// Copy the file
f = Fopen(fname, "rb");
// Prepare the directory entry
memset(&de, 0, sizeof de);
memcpy(de.Name, name8_3, 8 + 3);
de.Attribute = dea_ARCHIVE;
de.Size = size = fsize(f);
if (RootDirEntryIdx >= RootDirEntries ||
size < 0 || (unsigned long)size > Clusters * ClusterSize)
error("No space for file \"%s\"", fname);
if (size)
{
de.FirstClusterLoWord = Cluster;
de.FirstClusterHiWord = Cluster >> 16;
}
// TBD??? set file date/time to now?
de.LastWriteDate = ((1990 - 1980) << 9) | (1 << 5) | 1; // 1990/01/01
de.LastWriteTime = (12 << 11) | (0 << 5) | (0 >> 1); // 12(PM):00:00
// Seek both files
Fseek(f, 0, SEEK_SET);
ofs = Cluster2Lba + (Cluster - 2) * SectorsPerCluster;
Fseek(fout, ofs * 512, SEEK_SET);
// Copy data sectors
while (size)
{
uint8 sector[512];
long sz = (size > 512) ? 512 : size;
memset(sector, 0, 512); // pad with zeroes the last partial sector
Fread(sector, sz, f);
Fwrite(sector, 512, fout);
size -= sz;
}
// Allocate and chain clusters in the FAT
size = de.Size;
while (size)
{
if (size > (long)ClusterSize)
{
// There's at least one more cluster in the chain
ChainCluster(Cluster + 1);
size -= ClusterSize;
}
else
{
// No more clusters, this is the last one in the chain
ChainCluster(0xFF8);
size = 0;
}
Clusters--;
}
// Write the directory entry
AddRootDirEntry(&de);
Fclose(f);
}
int main(int argc, char* argv[])
{
int i;
for (i = 1; i < argc; i++)
{
if (!strcmp(argv[i], "-o"))
{
if (i + 1 < argc)
{
argv[i++] = NULL;
OutName = argv[i];
argv[i] = NULL;
continue;
}
}
else if (!strcmp(argv[i], "-bs"))
{
if (i + 1 < argc)
{
argv[i++] = NULL;
BootSectName = argv[i];
argv[i] = NULL;
continue;
}
}
else if (!strcmp(argv[i], "-us"))
{
UniqueSerial = 1;
argv[i++] = NULL;
continue;
}
if (argv[i][0] == '-')
error("Invalid or unsupported command line option\n");
}
Init();
for (i = 1; i < argc; i++)
if (argv[i])
AddFile(argv[i]);
Done();
return 0;
}