/*
* 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]