commit a6d33cae7cae445a239d9caa1b3676a97242f81f Author: snow flurry Date: Fri Nov 13 21:43:35 2020 -0800 Initial commit diff --git a/doc/cfg.txt b/doc/cfg.txt new file mode 100644 index 0000000..fbfb902 --- /dev/null +++ b/doc/cfg.txt @@ -0,0 +1,30 @@ +# <- This is a comment. Anything on a line after this character +# will be ignored. +# +# Configuration format is like this: +# +# filename ext start end skip-min skip-max options +# +# Where the individual options are: +# filename: The filename, without the extension. For example, +# "COMMAND.COM" would be referred to as COMMAND here. +# Case-insensitive, set to * to match any. +# ext: The file extension. Using the above example, this +# would be COM. Also accepts the * wildcard. +# start: The byte offset to start corrupting at. +# end: The byte offset to stop corrupting at. If set to 0, the +# corruption will continue until the end of the file. +# skip-min: Minimum bytes to skip between each corruption. +# skip-max: Maximum bytes to skip between each corruption. +# options: Comma-delimited list of the following: +# shr=X: Shift bytes right X times +# shl=X: Shift bytes left X times +# add=X: Add X to bytes +# xor=X: Bitwise XOR to bytes +# +# skip-min and skip-max are used as bounds for the actual random amount +# of bytes skipped each iteration. +# +# Some examples of possible configuration values are below: +* exe 200 0 500 700 xor=50,add=10 +* dat 100 0 200 250 shr=3 diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..8f414f5 --- /dev/null +++ b/src/config.c @@ -0,0 +1 @@ +/* TODO */ diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..0a76cc0 --- /dev/null +++ b/src/config.h @@ -0,0 +1,22 @@ +#ifndef _CONFIG_H +#define _CONFIG_H + +struct conf_opts_t { + uint8_t shr; + uint8_t shl; + uint8_t add; + uint8_t bit_xor; +}; + +typedef struct config_t { + char filename[9]; + char ext[4]; + uint16_t start; + uint16_t end; + uint16_t skip_a; + uint16_t skip_b; + +} config_t; + + +#endif /* !_CONFIG_H */ diff --git a/src/debug.h b/src/debug.h new file mode 100644 index 0000000..568e55c --- /dev/null +++ b/src/debug.h @@ -0,0 +1,10 @@ +#ifndef _DEBUG_H +#define _DEBUG_H + +#ifdef DEBUG +#define DPRINTF(x) printf(x) +#else +#define DPRINTF(x) +#endif + +#endif /* !_DEBUG_H */ diff --git a/src/dosfs.c b/src/dosfs.c new file mode 100644 index 0000000..007838b --- /dev/null +++ b/src/dosfs.c @@ -0,0 +1,272 @@ +#include +#include +#include +#include +#include +#include + +#include "dosfs.h" +#include "debug.h" + +/* + * Opens an image a la open(2). Returns 0 on success, or + * some error number on failure. + */ +int +open_image(const char *path, int flags, dosfs_t *fsd) +{ + int res, ifd; + + if (fsd == NULL) + return EINVAL; + + ifd = open(path, flags); + if (ifd == -1) + return errno; + + memset(&fsd->ib, 0, sizeof(struct bpb)); + + res = read(ifd, &fsd->ib, sizeof(struct bpb)); + if (res == -1) { + close(ifd); + return errno; + } + + if (fsd->ib.jmp[0] != 0xEB || fsd->ib.jmp[2] != 0x90) { + DPRINTF("hm... doesn't look like a FAT image?\n"); + close(ifd); + return EOPNOTSUPP; + } + + if (fsd->ib.sect_ct == 0 || fsd->ib.fat_size == 0) { + DPRINTF("sect_ct or fat_size == 0, likely fat>32 (unsupported)\n"); + close(ifd); + return EOPNOTSUPP; + } + + fsd->root_size = ((fsd->ib.dirent_ct * 32) + (fsd->ib.sect_size - 1)) / + fsd->ib.sect_size; + + fsd->data_size = fsd->ib.sect_ct - (fsd->ib.resv_sect + + (fsd->ib.fat_ct * fsd->ib.fat_size) + fsd->root_size); + + if (fsd->data_size < FAT12_MAX_CLUSTERS) { + fsd->fs_ver = 12; + } else if (fsd->data_size < FAT16_MAX_CLUSTERS) { + fsd->fs_ver = 16; + } else { + DPRINTF("data_size >= FAT12_MAX_CLUSTERS, likely fat>32 (unsupported)\n"); + close(ifd); + return EOPNOTSUPP; + } + + fsd->ifd = ifd; + fsd->root_loc = (fsd->ib.resv_sect + + (fsd->ib.fat_ct * fsd->ib.fat_size)) * fsd->ib.sect_size; + fsd->data_loc = fsd->root_loc + (fsd->root_size * fsd->ib.sect_size); + + return 0; +} + +/* + * Frees the file descriptor for a given image. + */ +void +close_image(dosfs_t *fsd) +{ + close(fsd->ifd); + fsd->ifd = -1; +} + +/* + * Reads directory listing at given offset. Returns a pointer + * to a dosfile_t on success, or NULL on failure (errno set). + */ +dosfile_t * +dos_listdir(dosfs_t *fsd, unsigned int offset) +{ + dosfile_t *start, *cur; + struct dos_dirent *dirs = NULL; + int i, j, res, root_ents; + + start = malloc(sizeof(dosfile_t)); + if (start == NULL) { + DPRINTF("malloc failed\n"); + return NULL; + } + + /* + * Not sure if we should be trusting the bpb for anything, + * but relying on it here will be better than reading every + * single entry one at a time. + */ + root_ents = (fsd->root_size * fsd->ib.sect_size) / + sizeof(struct dos_dirent); + + res = reallocarr(&dirs, root_ents, sizeof(struct dos_dirent)); + if (res == -1) { + DPRINTF("reallocarr failed\n"); + return NULL; + } + + res = pread(fsd->ifd, dirs, (root_ents * sizeof(struct dos_dirent)), + offset); + if (res == -1) { + DPRINTF("pread failed\n"); + return NULL; + } + + /* iterate through every dir entry */ + cur = start; + for (i = 0; i < root_ents; i++) { + if (dirs[i].filename[0] == 0) { + break; + } + + if (i > 0) { + cur->next = malloc(sizeof(dosfile_t)); + if (cur->next == NULL) { + DPRINTF("inner malloc failed!?\n"); + res = -1; + break; + } + + cur = cur->next; + } + + for (j = 0; j < 8; j++) { + if (dirs[i].filename[j] == ' ') { + cur->fname[j] = 0; + break; + } else { + cur->fname[j] = dirs[i].filename[j]; + } + } + cur->fname[8] = 0; + + for (j = 0; j < 3; j++) { + cur->fext[j] = dirs[i].filename[j+8]; + } + cur->fext[4] = 0; + + (void)memcpy(&cur->ent, &dirs[i], sizeof(struct dos_dirent)); + + + res = 0; + } + + free(dirs); + + /* cleanup if something went wrong */ + if (res != 0) { + dos_freedir(start); + return NULL; + } + + return start; +} + +/* + * Frees a directory listing. + */ +void +dos_freedir(dosfile_t *dirs) +{ + dosfile_t *cur, *prev; + + cur = dirs; + while (cur != NULL) { + prev = cur; + cur = cur->next; + free(prev); + } +} + +static unsigned int * +read_fat_chain(dosfs_t *fsd, unsigned int first, int *length) +{ + unsigned int cur, foff, byte_off, *chain = NULL; + uint16_t next; + int len = 0, res; + + cur = first; + for (;;) { + len++; + res = reallocarr(&chain, len, sizeof(unsigned int)); + if (res == -1) { + DPRINTF("reallocarr failed\n"); + free(chain); + return NULL; + } + + foff = (fsd->fs_ver == 12) ? (cur + (cur / 2)) : /* fat12 */ + (cur * 2); /* fat16 */ + byte_off = (fsd->ib.resv_sect * fsd->ib.sect_size) + foff; + + /* XXX: endianness */ + res = pread(fsd->ifd, &next, 2, byte_off); + if (res == -1) { + DPRINTF("pread failed\n"); + free(chain); + return NULL; + } + + if (fsd->fs_ver == 12) { + if (cur & 0x0001) { + next = next >> 4; + } else { + next = next & 0x0FFF; + } + if (next == 0x0FFF) { + break; + } + } else { + if (next == 0xFFFF) { + break; + } + } + + chain[len-1] = next; + } + + *length = len; + return chain; +} + +/* + * TODO: reads a file to a given buffer + */ +int +dos_fread(dosfs_t *fsd, dosfile_t *file, void *buf, int max_size) +{ + unsigned int *chain = NULL; + int clen = 0, i, left, chunk, cbytes, off, res; + + chain = read_fat_chain(fsd, file->ent.low_loc, &clen); + if (chain == NULL) { + return -1; /* XXX */ + } + + cbytes = fsd->ib.sect_size * fsd->ib.sect_per_cluster; + left = max_size; + + for (i = 0; i < clen; i++) { + /* only copy as much as we can hold */ + chunk = (left < cbytes) ? left : cbytes; + + res = pread(fsd->ifd, buf + (i * cbytes), + chunk, fsd->data_loc + (chain[i] * cbytes)); + if (res == -1) { + DPRINTF("pread failed\n"); + free(chain); + return -1; + } + + left -= chunk; + + if (left <= 0) + break; + } + + return 0; +} diff --git a/src/dosfs.h b/src/dosfs.h new file mode 100644 index 0000000..5f2d012 --- /dev/null +++ b/src/dosfs.h @@ -0,0 +1,84 @@ +/* fat12 */ + +#ifndef _FAT12_H +#define _FAT12_H + +struct __attribute__((__packed__)) bpb { + uint8_t jmp[3]; /* presumably some machine code */ + char oem_id[8]; + uint16_t sect_size; + uint8_t sect_per_cluster; + uint16_t resv_sect; + uint8_t fat_ct; + uint16_t dirent_ct; + uint16_t sect_ct; + uint8_t media_type; + uint16_t fat_size; + uint16_t track_size; + uint16_t heads; + uint32_t hidden_sect; + uint32_t larg_sect_ct; + /* + * XXX: Most PC98 disks don't seem to have the extended + * BPB. Should we even care about these? + */ + uint8_t drive_num; + uint8_t _reserved; + uint8_t signature; + uint32_t serial_num; + char label[11]; + char sys_id[8]; +}; + +struct __attribute__((__packed__)) dos_dirent { + char filename[11]; + uint8_t attributes; +#define ATTR_READONLY 0x01 +#define ATTR_HIDDEN 0x02 +#define ATTR_SYSTEM 0x04 +#define ATTR_VOLID 0x08 +#define ATTR_ISDIR 0x10 +#define ATTR_ARCHIVE 0x20 +#define ATTR_LFN (ATTR_READONLY|ATTR_HIDDEN|ATTR_SYSTEM|ATTR_VOLID) + uint8_t _reserved; + uint8_t creation_time_tenths; + uint16_t creation_time; + uint16_t creation_date; + uint16_t access_date; + uint16_t high_loc; /* only used with FAT32 */ + uint16_t mod_time; + uint16_t mod_date; + uint16_t low_loc; /* entry cluster location */ + uint32_t size; +}; + +#define FAT12_MAX_CLUSTERS 4085 +#define FAT16_MAX_CLUSTERS 65525 + +typedef struct dosfs_t { + struct bpb ib; + unsigned int fs_ver; + int ifd; + unsigned int root_loc; + unsigned int root_size; + unsigned int data_loc; + unsigned int data_size; +} dosfs_t; + +typedef struct dosfile_t { + struct dos_dirent ent; + char fname[9]; + char fext[4]; + struct dosfile_t *next; +} dosfile_t; + +int open_image(const char *, int, dosfs_t *); + +void close_image(dosfs_t *); + +dosfile_t *dos_listdir(dosfs_t *, unsigned int); +int dos_fread(dosfs_t *, dosfile_t *, void *, int); +void dos_freedir(dosfile_t *); + + +#endif /* !_FAT12_H */ diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..883b5b4 --- /dev/null +++ b/src/main.c @@ -0,0 +1,86 @@ +#include +#include +#include +#include + +#include "dosfs.h" + +static void usage(void) __dead; +static int list_img(char *); + +int +main(int argc, char *argv[]) +{ + int ch, list_mode; + + setprogname(argv[0]); + + list_mode = 0; + while ((ch = getopt(argc, argv, "l")) != -1) { + switch (ch) { + case 'l': + list_mode = 1; + break; + case '?': + default: + usage(); + /* NOTREACHED */ + } + } + argc -= optind; + argv += optind; + + if (argc != 1) { + usage(); + /* NOTREACHED */ + } + + if (list_mode) { + return list_img(argv[0]); + } + + /* TODO: rest of the owl */ + fprintf(stderr, "not yet implemented :(\n"); + + return EXIT_FAILURE; +} + +static int +list_img(char *path) +{ + dosfs_t img = { 0 }; + dosfile_t *rootdir, *cur; + int res; + + res = open_image(path, O_RDONLY, &img); + if (res != 0) { + fprintf(stderr, "err: couldn't open image (errno %d)\n", res); + return EXIT_FAILURE; + } + + rootdir = dos_listdir(&img, img.root_loc); + if (rootdir == NULL) { + perror("couldn't read root dir"); + close_image(&img); + return EXIT_FAILURE; + } + + printf("OEM ID: %.8s\nDirectory listing:\n", img.ib.oem_id); + for (cur = rootdir; cur->next != NULL; cur = cur->next) { + printf(" %s.%s\n", cur->fname, cur->fext); + } + + dos_freedir(rootdir); + rootdir = cur = NULL; + close_image(&img); + + return EXIT_SUCCESS; +} + +static void +usage(void) +{ + fprintf(stderr, "usage: %s [-l] image\n", getprogname()); + exit(EXIT_FAILURE); +} +