/* * JR-IDE Project * - (c) 2017 Alan Hightower * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #define DEFAULT_FILL 0xff #define USE_FILE_TIME 0 #define SECTOR_SIZE 512UL #define ROMDISK_SPT 8UL #define ROMDISK_HEADS 2UL #define RESERVED_SECTORS 1UL // basically the boot sector #define FAT_COUNT 2UL // PC-DOS 5.x assumes 2 - weird #define SECTORS_PER_CLUSTER 1UL // for up to 14 files per dir w/o resize #define SECTORS_PER_FAT 3UL // support max 512 KB #define ROOT_ENTRIES 16UL // Just need one sector #define LOGERR(fmt, ...) fprintf(stderr, "\nERROR: " fmt "\n\n", ## __VA_ARGS__) static uint8_t *image = NULL; static int size_kb = 512 - 16; // flash size - option rom window static int ioffset = 0; // offset of next available allocation static int storage = 0; // begining of cluster allocations static int part_start = 0; // LBA static int part_size = 0; // in sectors static int fat_start = 0; // image offset to first FAT sector static int fat_total = 0; // max number of FAT entries static int *fat_entry = NULL; // Unpacked FAT array static int fat_entries = 0; // Number of used FAT entries static int total_clusters = 0; static int root_start = 0; // image offset to root directory cluster static int root_entries = 0; // number of used root directory entries static int stat_dirs = 1; static int stat_files = 0; static uint16_t global_date = 0; static uint16_t global_time = 0; // Directory entry attributes #define A_READONLY (0x01) #define A_HIDDEN (0x02) #define A_SYSTEM (0x04) #define A_VOLUME (0x08) #define A_DIRECTORY (0x10) #define A_ARCHIVE (0x20) static int _file_load (const char *path, uint8_t **ptr) { int length, fd; struct stat stinfo; if (stat (path, &stinfo)) return -1; length = stinfo.st_size; uint8_t *data = (uint8_t *) malloc (length); if ((fd = open (path, O_RDONLY)) < 0) return -2; if (read (fd, data, length) != length) { close (fd); return -3; } close (fd); *ptr = data; return length; } void hexdump (uint32_t offset, const void *data, int len) { int i, addr = 0; uint8_t *ptr = (uint8_t *) data; while (len > 0) { printf (" 0x%08x:", (unsigned int) (addr + offset)); for (i = 0; i < 16; i++) { if (i < len) { printf (" %02x", ptr[i]); } else puts (" "); if (i == 7) putc (' ', stdout); } putc (' ', stdout); for (i = 0; i < 16; i++) { if (i < len) { printf ("%c", isalnum(ptr[i]) ? ptr[i] : '.'); } if (i == 7) putc (' ', stdout); } len -= 16; ptr += 16; addr += 16; putc ('\n', stdout); } return; } // http://pierrelib.pagesperso-orange.fr/exec_formats/OMF_v1.1.pdf #define MBRNAME "_MBRSEG" #define BOOTNAME "_BOOTSEG" static int _proc_omf (const char *path) { uint8_t *data = NULL, *ptr; int length = _file_load (path, &data); if (length < 4) return -1; int remain = length; ptr = data; int segidx = 1; int mbrname = -1, mbrseg = -1; int bootname = -1, bootseg = -1; while (remain >= 4) { int type = ptr[0]; int rlen = (((int) ptr[2]) << 8) | ptr[1]; if ((rlen + 3) > remain) break; int csumf = ptr[rlen + 2]; int csumc = 0, i; for (i = 0; i < (rlen + 3); i++) csumc += ptr[i]; csumc = (~csumc + 1) & 0xff; if (csumf && csumc) { LOGERR("Checksum mismatch in '%s'", path); break; } switch (type) { case 0x80: // THEADR - Translator Header case 0x88: // COMMENT - Comment case 0x8a: // MODEND - Module End case 0x90: // PUBDEF - Public Names Definition case 0x94: // LINNUM - Line Numbers case 0x9a: // GRPDEF - Group Name Definition case 0x9c: // FIXUPP - Linker Fixup/Relocations break; // ignore case 0x96: // LNAMES - List of names { int idx = 1; int rem = rlen - 1; char *name = (char *) (ptr + 3); while (rem) { if (rem <= *name) break; if ((*name == sizeof (MBRNAME) - 1) && (memcmp (&name[1], MBRNAME, sizeof (MBRNAME) - 1) == 0)) mbrname = idx; if ((*name == sizeof (BOOTNAME) - 1) && (memcmp (&name[1], BOOTNAME, sizeof (BOOTNAME) - 1) == 0)) bootname = idx; rem -= (*name + 1); name += (*name + 1); idx++; } break; } case 0x98: // SEGDEF - Segement definition { int seglen = (((int) ptr[5]) << 8) | ptr[4]; if (seglen && ((int) ptr[6] == mbrname)) mbrseg = segidx; if (seglen && ((int) ptr[6] == bootname)) bootseg = segidx; segidx++; break; } case 0xa0: // LEDATA - Load data { int offset = (((int) ptr[5]) << 8) | ptr[4]; int end = offset + rlen - 4; if ((int) ptr[3] == mbrseg) { /* * The MBR bootstrap code relocates itself to * 0000:0600 and then continues to execute. * Thus the bounds must be between the load * address of 0600h and the start of the * partition table at +01b8 */ if ((offset < 0x0600) || (end >= 0x07b8)) { LOGERR("MBR segment out of bounds %04xh - %04xh", offset, end); break; } printf (" - Loading MBR bootstrap code (%d bytes)\n", rlen - 4); uint8_t *mbr = image; memcpy (&mbr[offset - 0x600], ptr + 6, rlen - 4); if (mbr[0x1be] == 0x80) break; printf (" - Creating active partition 1\n"); mbr[0x1b8] = 0; // disk signature mbr[0x1b9] = 0; mbr[0x1ba] = 0; mbr[0x1bb] = 0; mbr[0x1bc] = 0; // inter-filler mbr[0x1bd] = 0; // First partition table entry int total_cyls = (size_kb * 1024L) / (SECTOR_SIZE * ROMDISK_HEADS * ROMDISK_SPT); mbr[0x1be] = 0x80; // active bit mbr[0x1c2] = 0x01; // type: FAT12 int c, h, s, lba; // Start c = 0; h = 1; s = 1; lba = c * ROMDISK_SPT * ROMDISK_HEADS; lba += h * ROMDISK_SPT; lba += s - 1; mbr[0x1bf] = h; mbr[0x1c0] = s; mbr[0x1c1] = c; mbr[0x1c6] = (lba ) & 0xff; mbr[0x1c7] = (lba >> 8) & 0xff; mbr[0x1c8] = (lba >> 16) & 0xff; mbr[0x1c9] = (lba >> 24) & 0xff; ioffset = lba * SECTOR_SIZE; part_start = lba; // End c = total_cyls - 1; h = ROMDISK_HEADS - 1; s = ROMDISK_SPT; lba = c * ROMDISK_SPT * ROMDISK_HEADS; lba += h * ROMDISK_SPT; lba += s - 1; part_size = lba - part_start + 1; mbr[0x1c3] = h; mbr[0x1c4] = s; mbr[0x1c5] = c; mbr[0x1ca] = (part_size ) & 0xff; mbr[0x1cb] = (part_size >> 8) & 0xff; mbr[0x1cc] = (part_size >> 16) & 0xff; mbr[0x1cd] = (part_size >> 24) & 0xff; // clear last 3 entries memset (&mbr[0x1ce], 0, 48); // Add MBR signature mbr[0x1fe] = 0x55; mbr[0x1ff] = 0xaa; break; } if ((int) ptr[3] == bootseg) { /* * The DOS bootstrap code does not relocate * but it does overwrite itself to extend the * boot sector table to include the Floppy * Disk Parameters Table (FDPT) and local * variables */ if ((offset < 0x7c00) || (end >= 0x7dfe)) { LOGERR("BOOT segment out of bounds %04xh - %04xh", offset, end); break; } printf (" - Loading DOS bootstrap code (%d bytes)\n", rlen - 4); uint8_t *boot = &image[ioffset]; memcpy (&boot[offset - 0x7c00], ptr + 6, rlen - 4); #define BOOT_SIGNATURE 0x29 if (boot[0x26] != BOOT_SIGNATURE) printf (" - Updating DOS boot sector\n"); // Add boot sector information memcpy (&boot[0x03], "IBM 5.0", 8); // OEM ID boot[0x0b] = SECTOR_SIZE & 0xff; boot[0x0c] = (SECTOR_SIZE >> 8) & 0xff; boot[0x0d] = SECTORS_PER_CLUSTER; boot[0x0e] = RESERVED_SECTORS & 0xff; boot[0x0f] = (RESERVED_SECTORS >> 8) & 0xff; boot[0x10] = FAT_COUNT; boot[0x11] = ROOT_ENTRIES & 0xff; boot[0x12] = (ROOT_ENTRIES >> 8) & 0xff; boot[0x13] = part_size & 0xff; boot[0x14] = (part_size >> 8) & 0xff; boot[0x15] = 0xf8; // Media descriptor boot[0x16] = SECTORS_PER_FAT & 0xff; boot[0x17] = (SECTORS_PER_FAT >> 8) & 0xff; boot[0x18] = ROMDISK_SPT & 0xff; boot[0x19] = (ROMDISK_SPT >> 8) & 0xff; boot[0x1a] = ROMDISK_HEADS & 0xff; boot[0x1b] = (ROMDISK_HEADS >> 8) & 0xff; boot[0x1c] = (part_start ) & 0xff; // hidden boot[0x1d] = (part_start >> 8) & 0xff; // sectors boot[0x1e] = (part_start >> 16) & 0xff; boot[0x1f] = (part_start >> 24) & 0xff; boot[0x20] = 0x00; // DD: Total sectors boot[0x21] = 0x00; // (0 for FAT12) boot[0x22] = 0x00; boot[0x23] = 0x00; boot[0x24] = 0x80; // BIOS drive ID boot[0x25] = 0x00; // Ignored / clobbered boot[0x26] = BOOT_SIGNATURE; boot[0x27] = 0xaf; // Vol ID: 0xdeadbeaf boot[0x28] = 0xbe; boot[0x29] = 0xad; boot[0x2a] = 0xde; memcpy (&boot[0x2b], "ROMDISK ", 11); // Vol Label memcpy (&boot[0x36], "FAT12 ", 8); // FAT type // Add BOOT signature boot[0x1fe] = 0x55; boot[0x1ff] = 0xaa; break; } // Discard all other segment data break; } default: LOGERR("Unknown OMF type %x (%d bytes) in '%s'", type, rlen - 1, path); hexdump (0, ptr + 3, rlen - 1); break; } ptr += rlen + 3; remain -= rlen + 3; } free (data); return remain ? -1 : 0; } typedef struct _st_dosdir { char name[8]; char ext[3]; uint8_t attrib; uint16_t reserved; uint16_t create_time; uint16_t create_date; uint16_t access_date; uint16_t ignore; uint16_t write_time; uint16_t write_date; uint16_t cluster; uint32_t size; } __attribute__((__packed__)) dosdir_t; static int parse_name (char *path, int line, char **base_ptr, char **ext_ptr) { char *dot, *base = path, *ext = ""; if ((dot = strchr (base, '.'))) { *dot = '\0'; ext = dot + 1; } if ((strlen (base) > 8) || (strlen (ext) > 3)) { LOGERR("Base name '%s%s%s' not in 8.3 @ line %d", path, ext[0] ? "." : "", ext[0] ? ext : "", line); return -1; } *base_ptr = base; *ext_ptr = ext; return 0; } static int add_entry (char *src, char *dst, int attrib, int line) { int sts = -1; int *dents = &root_entries; int max_dents = ROOT_ENTRIES; int doffset = root_start; int parent_cluster = 0; dosdir_t entry; memset (&entry, 0, sizeof (entry)); memset (entry.name, ' ', 11); if (dst) { int i, len = strlen (dst); for (i = 0; i < len; i++) dst[i] = toupper(dst[i]); } if (attrib & A_VOLUME) { if (root_entries == max_dents) { LOGERR("Root directory full (%d)", root_entries); return -1; } int len = strlen (src); if (len > 11) len = 11; memcpy (entry.name, src, len); printf (" - Added label '%s'\n", entry.name); entry.attrib = attrib; entry.write_time = htole16(0x003d); // 00:01:58 a JOEWEED entry.write_date = htole16(0x0021); // Jan 1, 1980 doffset += root_entries * sizeof (dosdir_t); memcpy (&image[doffset], &entry, sizeof (entry)); root_entries++; return 0; } char *base, *ext; char *oname = strdup (dst); // mkdir -p on dirpath int sub_entries = 0, error = 0; while (!error) { char *ch1 = strchr (dst, '/'); char *ch2 = strchr (dst, '\\'); if (!ch1 && !ch2) break; char *ch = !ch2 ? ch1 : !ch1 ? ch2 : ch1 < ch2 ? ch1 : ch2; *ch = '\0'; char *dir = dst; dst = ch + 1; // Search current directory dosdir_t dentry; memset (&dentry, 0, sizeof (entry)); memset (dentry.name, ' ', 11); if (parse_name (dir, line, &base, &ext)) return -1; memcpy (dentry.name, base, strlen (base)); memcpy (dentry.ext, ext, strlen (ext)); dosdir_t *node = (dosdir_t *) &image[doffset]; dosdir_t *free = NULL; for (sub_entries = 0; sub_entries < max_dents; sub_entries++) { if (memcmp (node->name, dentry.name, 11) == 0) break; if (!node->name[0] && !free) free = node; node++; } if (sub_entries == max_dents) { // not found if (!free || (total_clusters == fat_entries)) { LOGERR("Out of dirents @ line %d", line); error = 1; /* * A potential fix for this is to allocate * the next free cluster and keep expanding * (what real DOS does) */ break; } // Fill in meta data & add to current dir dentry.attrib = A_DIRECTORY; dentry.cluster = fat_entries; dentry.create_time = global_time; dentry.create_date = global_date; dentry.access_date = global_date; dentry.write_time = global_time; dentry.write_date = global_date; memcpy (free, &dentry, sizeof (dosdir_t)); (*dents)++; /* * Allocate only 1 cluster and add '.' & '..' * * We could support multiple cluster dirents, * but the simplest solution if we run out in * this embedded FAT12 use-case is just to * increase the sectors per cluster * * Dynamically expanding like real DOS does * would be another option. */ int dsize = 1 * SECTORS_PER_CLUSTER * SECTOR_SIZE; sub_entries = 2; dents = &sub_entries; max_dents = dsize / sizeof (dosdir_t); doffset = ioffset; free = (dosdir_t *) &image[doffset]; memset (free, 0, dsize); memcpy (free->name, ". ", 11); free->create_time = global_time; free->create_date = global_date; free->access_date = global_date; free->write_time = global_time; free->write_date = global_date; free->attrib = A_DIRECTORY; free->cluster = dentry.cluster; free++; memcpy (free->name, ".. ", 11); free->create_time = global_time; free->create_date = global_date; free->access_date = global_date; free->write_time = global_time; free->write_date = global_date; free->attrib = A_DIRECTORY; free->cluster = parent_cluster; parent_cluster = dentry.cluster; fat_entry[fat_entries] = 0xfff; fat_entries++; ioffset += dsize; break; } doffset = storage + ((node->cluster - 2) * SECTORS_PER_CLUSTER * SECTOR_SIZE); node = (dosdir_t *) &image[doffset]; int dsize = 1 * SECTORS_PER_CLUSTER * SECTOR_SIZE; max_dents = (dsize / sizeof (dosdir_t)); for (sub_entries = 0; sub_entries < max_dents; sub_entries++) { if (!node->name[0]) break; node++; } dents = &sub_entries; } if (error) return -1; // Groom file name if (parse_name (dst, line, &base, &ext)) return -1; memcpy (entry.name, base, strlen (base)); memcpy (entry.ext, ext, strlen (ext)); uint32_t csize = SECTORS_PER_CLUSTER * SECTOR_SIZE; uint32_t dsize = 0; uint8_t *data = NULL; struct stat stinfo; do { if (stat (src, &stinfo)) { LOGERR("Unable to stat '%s' @ line %d", src, line); break; } dsize = (stinfo.st_size + csize - 1UL) & ~(csize - 1UL); int clusters = dsize / (SECTORS_PER_CLUSTER * SECTOR_SIZE); if ((*dents == max_dents) || (clusters > (total_clusters - fat_entries))) { LOGERR("No free space for '%s'", src); break; } entry.attrib = attrib; entry.cluster = fat_entries; entry.size = stinfo.st_size; #if USE_FILE_TIME struct tm local; localtime_r (&stinfo.st_mtime, &local); entry.create_time = (local.tm_hour << 11) | (local.tm_min << 5) | (local.tm_sec / 2); entry.create_date = ((local.tm_year - 80) << 9) | ((local.tm_mon + 1) << 5) | local.tm_mday; entry.access_date = entry.create_date; entry.write_time = entry.create_time; entry.write_date = entry.create_date; #else entry.create_time = global_time; entry.create_date = global_date; entry.access_date = global_date; entry.write_time = global_time; entry.write_date = global_date; #endif // add dirent doffset += *dents * sizeof (dosdir_t); memcpy (&image[doffset], &entry, sizeof (entry)); (*dents)++; // Update storage if (_file_load (src, &data) != stinfo.st_size) break; memcpy (&image[ioffset], data, stinfo.st_size); memset (&image[ioffset + stinfo.st_size], 0, dsize - stinfo.st_size); int c; for (c = 0; c < clusters; c++) { if ((c + 1) == clusters) fat_entry[fat_entries] = 0xfff; else fat_entry[fat_entries] = fat_entries + 1; fat_entries++; } ioffset += dsize; sts = 0; } while (0); printf (" - Added file '%s' %*s/ %6d bytes / %6d on disk\n", oname, 20 - (int) strlen (oname), "", (int) stinfo.st_size, dsize); stat_files++; if (data) free (data); return sts; } static void _print_usage (const char *progname) { fprintf (stderr, "\n" "%s [options] \n" "\n" "Options:\n" "\n" "\t-k - Specify rom disk image size in KB\n" "\r-p - Pre-output n bytes of zero to pad ROM\n" "\n\n", progname); return; } int main (int argc, char *argv[]) { int sts = -1, pad = 0; char *progname = basename (argv[0]); char *path_image = NULL; char *path_object = NULL; char *path_manifest = NULL; argc--; argv++; while (argc) { if ((argc > 1) && !strcmp (argv[0], "-k")) { argc--; argv++; size_kb = atoi (argv[0]); } else if ((argc > 1) && !strcmp (argv[0], "-p")) { argc--; argv++; pad = atoi (argv[0]); } else if (!path_image) { path_image = argv[0]; } else if (!path_object) { path_object = argv[0]; } else if (!path_manifest) { path_manifest = argv[0]; } else { _print_usage (progname); break; } argc--; argv++; } if (!path_image || !path_object || !path_manifest) { _print_usage (progname); return -1; } printf (" -MKFS- Creating %dKB '%s'\n", size_kb, path_image); global_time = (22 << 11) | (30 << 5); // 11:30pm global_date = (39 << 9) | (1 << 5) | 1; // Jan 1, 2019 global_time = htole16(global_time); global_date = htole16(global_date); do { int total_size = size_kb * 1024; if (!(image = (uint8_t *) malloc (total_size))) { LOGERR("Unable to allocate image - %s", strerror (errno)); break; } memset (image, DEFAULT_FILL, total_size); // Insert MBR and boot sector & create partition if (_proc_omf (path_object)) { LOGERR("Unable to load '%s'", path_object); break; } if ((image[0x1fe] != 0x55) || (image[0x1ff] != 0xaa)) { LOGERR("Unable to insert MBR"); break; } if (!ioffset) { LOGERR("Unable to insert boot sector"); break; } ioffset += RESERVED_SECTORS * SECTOR_SIZE; // Allocate FAT tables fat_start = ioffset; fat_total = (SECTORS_PER_FAT * SECTOR_SIZE * 8L / 12L) & ~1L; fat_entry = (int *) calloc (sizeof (int), fat_total); fat_entry[0] = 0xff8; fat_entry[1] = 0xfff; fat_entries = 2; memset (&image[ioffset], 0, FAT_COUNT * SECTORS_PER_FAT * SECTOR_SIZE); ioffset += FAT_COUNT * SECTORS_PER_FAT * SECTOR_SIZE; // Allocate root directory root_start = ioffset; root_entries = 0; int root_size = ((ROOT_ENTRIES * 32) + SECTOR_SIZE - 1) & ~(SECTOR_SIZE - 1); memset (&image[ioffset], 0, root_size); ioffset += root_size; storage = ioffset; total_clusters = ((total_size - ioffset) / (SECTORS_PER_CLUSTER * SECTOR_SIZE)) + 2; if (total_clusters > fat_total) { LOGERR("Not enough FAT entries to support disk size (%d/%d)", total_clusters, fat_total); break; } // Process files from manifest FILE *handle; handle = fopen (path_manifest, "r"); if (!handle) { LOGERR("Unable to open manifest '%s' - %s", path_manifest, strerror (errno)); break; } #define IPATTERN "\t\r\n " int line = 0; char buffer[1024], *ptr, *tok; while ((ptr = fgets (buffer, sizeof (buffer), handle))) { line++; if (*ptr == '#') continue; if ((tok = strchr (ptr, ';'))) *tok = '\0'; int attrib = A_ARCHIVE; char *src = NULL, *dst = NULL; if (!(tok = strtok (ptr, IPATTERN))) continue; while (tok) { if (tok[0] == '+') { int error = 0; tok++; while (*tok) { switch (*tok) { case 'r': case 'R': attrib |= A_READONLY; break; case 'h': case 'H': attrib |= A_HIDDEN; break; case 'v': case 'V': attrib |= A_VOLUME; break; case 's': case 'S': attrib |= A_SYSTEM; break; case 'a': case 'A': attrib |= A_ARCHIVE; break; default: error = 1; break; } if (error) break; tok++; } if (error) break; } else if (!src) src = tok; else if (!dst) dst = tok; else break; tok = strtok (NULL, IPATTERN); } if (tok) { LOGERR("Unknown '%s' @ %s:%d", tok, path_manifest, line); break; } if (!src) { LOGERR("Must supply source @ %s:%d", path_manifest, line); break; } if (!(attrib & A_VOLUME) && !dst) { LOGERR("Must supply destibation @ %s:%d", path_manifest, line); break; } if (add_entry (src, dst, attrib, line)) break; } fclose (handle); if (ptr) break; // Update FAT sectors int i, c, f; for (f = 0; f < (int) FAT_COUNT; f++) { uint8_t *ptr = &image[fat_start + (f * SECTORS_PER_FAT * SECTOR_SIZE)]; for (i = 0, c = 0; c < fat_total; i += 3, c += 2, ptr += 3) { int a = fat_entry[c]; int b = fat_entry[c + 1]; ptr[0] = a & 0xff; ptr[1] = ((a >> 8) & 0x0f) | ((b & 0x0f) << 4); ptr[2] = (b >> 4) & 0xff; } } // Print stats if (stat_dirs == 1) printf (" - 1 total directory\n"); else printf (" - %d total directories\n", stat_dirs); if (stat_files == 1) printf (" - 1 total file\n"); else printf (" - %d total files\n", stat_files); printf (" - %ld bytes free\n", (total_clusters - fat_entries) * SECTORS_PER_CLUSTER * SECTOR_SIZE); // Write out output file unlink (path_image); handle = fopen (path_image, "w+"); if (!handle) { LOGERR("Unable to create output '%s' - %s", path_image, strerror (errno)); break; } if (pad) { uint8_t *buffer = (uint8_t *) alloca (pad); memset (buffer, DEFAULT_FILL, pad); fwrite (buffer, 1, pad, handle); } int osize = fwrite (image, 1, total_size, handle); fclose (handle); if (osize != total_size) { LOGERR("Unable to write output '%s' - %s", path_image, strerror (errno)); break; } printf (" - Wrote %sROM disk image - %d bytes\n", pad ? "padded " : "", total_size + pad); sts = 0; } while (0); if (fat_entry) free (fat_entry); if (image) free (image); return sts; }