#define _POSIX_C_SOURCE 200809L #include "lzw.h" #include "utils.h" #include #include #include #include #include #include #undef DEBUG #define MAGIC_DSK_OLD 0x58aa #define MAGIC_DSK_UNCOMPRESSED 0x59aa #define MAGIC_DSK_COMPRESSED 0x5aaa #if defined(__DOS__) || defined(__WINDOWS__) || defined(__NT__) || defined(__OS2__) #define DOSLIKE #define DIRSEP '\\' #include /* For _chsize() */ #else #undef DOSLIKE #define DIRSEP '/' #endif #ifdef __I86__ /* Use smaller buffers in 16bit environments */ #define READ_BUFFER_SIZE 1024 #define DECOMPRESS_BUFFER_SIZE 4096 #define COPY_BUFFER_SIZE 8192 #else #define READ_BUFFER_SIZE 4096 #define DECOMPRESS_BUFFER_SIZE 4096 #define COPY_BUFFER_SIZE 8192 #endif #pragma pack(1) struct dskheader { uint16_t magic; /* magic identifier (0x58aa, 0x59aa or 0x5aaa) */ uint16_t mediatype; /* disk media type as defined in the first byte of the FAT */ uint16_t sectorsize; /* sector size in bytes */ uint8_t clustermask; /* number of sectors per cluster minus one */ uint8_t clustershift; /* log2(cluster size / sector size) */ uint16_t reservedsectors; /* number of reserved sectors at the start of the disk */ uint8_t fatcopies; /* number of copies of the FAT */ uint16_t rootentries; /* max number of entries in the root directory */ uint16_t firstclustersector; /* count of sectors for boot sector + FATs + root dir */ uint16_t imageclusters; /* number of clusters in the image (empty clusters at the end are not saved) */ uint8_t sectorsperfat; /* size of each FAT copy in sectors */ uint16_t rootdirsector; /* count of sectors used by boot sector + FATs */ uint32_t checksum; /* sum of all words in the image */ uint16_t cylinders; /* number of disk cylinders (40 or 80) */ uint16_t heads; /* number of disk heads (sides, 1 or 2) */ uint16_t sectors; /* number of sectors per track */ uint32_t unused; uint16_t imagesectors; /* number of sectors in the image */ uint16_t commentoffset; /* offset of the image comment string */ uint16_t firstoffset; /* offset of the start of image contents */ }; #pragma pack() const struct media_type { int sectorsize; int cylinders; int heads; int sectors; char* name; } media_types[] = { { 512, 80, 2, 36, "3.5\" DSED 2.88MB" }, { 512, 80, 2, 18, "3.5\" DSHD 1.44MB" }, { 512, 80, 2, 9, "3.5\" DSDD 720KB" }, { 512, 80, 2, 15, "5.25\" DSHD 1.2MB" }, { 512, 40, 2, 9, "5.25\" DSDD 360KB" }, { 512, 40, 1, 8, "5.25\" SSDD 160KB" }, { 512, 40, 1, 9, "5.25\" SSDD 180KB" }, { 512, 40, 2, 8, "5.25\" DSDD 320KB" }, { 512, 80, 1, 8, "5.25\" SSQD 320KB" }, { 512, 80, 2, 8, "5.25\" DSQD 640KB" }, { 512, 80, 1, 8, "3.5\" SSDD 320KB" }, { 512, 80, 1, 9, "3.5\" SSDD 360KB" }, { 512, 80, 2, 8, "3.5\" DSDD 640KB" }, { 512, 80, 2, 21, "3.5\" DSHD 1.68MB DMF" }, { 512, 80, 2, 21, "3.5\" DSHD 1.72MB DMF" }, { -1, -1, -1, -1, "Unknown" } }; char* guess_output_filename(const char* input_filename, int is_compressed) { char* outname; unsigned int ifnlen, ofnlen; int i; ifnlen = strlen(input_filename); ofnlen = ifnlen + 9; outname = (char*)malloc(ofnlen); strlcpy(outname, input_filename, ofnlen); /* strip extension */ for (i = ifnlen; i > 0; i--) { if (outname[i - 1] == '.') { outname[i - 1] = '\0'; break; } else if (outname[i - 1] == DIRSEP) break; } #ifdef DOSLIKE if (is_compressed) /* use filename.lzw instead of filename.img.lzw on dos and win */ strlcat(outname, ".lzw", ofnlen); else strlcat(outname, ".img", ofnlen); #else strlcat(outname, ".img", ofnlen); if (is_compressed) strlcat(outname, ".lzw", ofnlen); #endif return outname; } void pad_file_to_size(FILE* f, unsigned long size) { long current_pos; current_pos = ftell(f); if (current_pos < 0) { perror("ftell()"); } if ((unsigned long)current_pos > size) printf("ERROR: output file is larger than expected! (%ld vs %lu)\n", current_pos, size); else if ((unsigned long)current_pos != size) { #ifdef DOSLIKE _chsize(fileno(f), (long)size); #else ftruncate(fileno(f), size); #endif } } int valid_header(const struct dskheader* h) { unsigned long int final_sectors, cluster_sectors, final_bytes; if (!(h->magic == MAGIC_DSK_COMPRESSED || h->magic == MAGIC_DSK_UNCOMPRESSED || h->magic == MAGIC_DSK_OLD)) return 0; if (h->cylinders < 40 || h->cylinders > 80) return 0; if (h->heads == 0 || h->heads > 2) return 0; if (h->sectors < 8 || h->sectors > 36) return 0; /* reasonable floppy sector sizes are powers of two between 128 and 1024 */ if (h->sectorsize < 128 || h->sectorsize > 1024 || (h->sectorsize & (h->sectorsize - 1)) != 0) return 0; final_sectors = h->cylinders * h->heads * h->sectors; cluster_sectors = ((unsigned long int)h->imageclusters) << h->clustershift; final_bytes = final_sectors * h->sectorsize; if (final_bytes > 4000000UL) return 0; if ((h->clustershift > 7) || (h->clustermask != (1UL << h->clustershift) - 1)) return 0; if (h->reservedsectors + h->fatcopies * h->sectorsperfat != h->rootdirsector) return 0; /* if (h->firstclustersector - 1 + cluster_sectors != final_sectors) return 0; */ if (h->firstclustersector != h->reservedsectors + h->fatcopies * h->sectorsperfat + h->rootentries * 32 / h->sectorsize) return 0; if (h->imagesectors > final_sectors) return 0; if (h->commentoffset < sizeof(struct dskheader) || h->firstoffset <= h->commentoffset) return 0; return 1; } #ifdef DEBUG void dump_dsk_header(struct dskheader* h) { printf("=== header =====================================\n"); printf("magic: 0x%04x\n", h->magic); printf("media type: 0x%04x\n", h->mediatype); printf("sectorsize: %d\n", h->sectorsize); printf("clustermask: %d\n", h->clustermask); printf("clustershift: %d\n", h->clustershift); printf("reservedsectors: %d\n", h->reservedsectors); printf("fatcopies: %d\n", h->fatcopies); printf("rootentries: %d\n", h->rootentries); printf("firstclustersector: %d\n", h->firstclustersector); printf("imageclusters: %d\n", h->imageclusters); printf("sectorsperfat: %d\n", h->sectorsperfat); printf("rootdirsector: %d\n", h->rootdirsector); printf("checksum: 0x%08x\n", h->checksum); printf("cylinders: %d\n", h->cylinders); printf("heads: %d\n", h->heads); printf("sectors: %d\n", h->sectors); printf("unused: 0x%08x\n", h->unused); printf("imagesectors: %d\n", h->imagesectors); printf("commentoffset: %d\n", h->commentoffset); printf("firstoffset: %d\n", h->firstoffset); printf("================================================\n"); } #endif void update_checksum(uint32_t* checksum, uint8_t* buffer, size_t size) { uint16_t* p = (uint16_t*)buffer; size_t i; for (i = 0; i < size / 2; i++) *checksum += p[i]; if (size & 1) *checksum += buffer[size - 1] << 8; } int copy_image_data(FILE* fin, FILE* fout, size_t size, uint32_t* checksum) { unsigned long int copied_bytes; size_t read_count, write_count; uint8_t* copy_buffer = NULL; copy_buffer = (uint8_t*)malloc(COPY_BUFFER_SIZE); if (copy_buffer == NULL) { fputs("Can't allocate read buffer.", stderr); return -1; } for (copied_bytes = 0; copied_bytes < size;) { read_count = fread(copy_buffer, 1, COPY_BUFFER_SIZE, fin); if (read_count == 0) { perror("fread()"); free(copy_buffer); return -2; } update_checksum(checksum, copy_buffer, read_count); write_count = fwrite(copy_buffer, 1, read_count, fout); if (read_count != write_count) { perror("fwrite()"); free(copy_buffer); return -3; } copied_bytes += read_count; } free(copy_buffer); return 0; } int copy_compressed_image_data(FILE* fin, FILE* fout) { size_t read_count, write_count; uint8_t* copy_buffer = NULL; copy_buffer = (uint8_t*)malloc(COPY_BUFFER_SIZE); if (copy_buffer == NULL) { fputs("Can't allocate read buffer.", stderr); return -1; } for (;;) { read_count = fread(copy_buffer, 1, COPY_BUFFER_SIZE, fin); if (read_count == 0) { perror("fread()"); free(copy_buffer); return -2; } write_count = fwrite(copy_buffer, 1, read_count, fout); if (read_count != write_count) { perror("fwrite()"); free(copy_buffer); return -3; } if (read_count < COPY_BUFFER_SIZE) break; } free(copy_buffer); return 0; } int decompress_image_data(FILE* fin, FILE* fout, size_t size, uint32_t* checksum) { unsigned long int copied_bytes = 0; size_t read_available, read_consumed, decompress_consumed; uint8_t *read_buffer, *decompress_buffer; struct lzw_ctx* lzw = NULL; read_buffer = (uint8_t*)malloc(READ_BUFFER_SIZE); if (read_buffer == NULL) { fputs("Can't allocate read buffer.", stderr); return -1; } decompress_buffer = (uint8_t*)malloc(DECOMPRESS_BUFFER_SIZE); if (decompress_buffer == NULL) { fputs("Can't allocate decompress buffer.", stderr); free(read_buffer); return -2; } lzw = (struct lzw_ctx*)malloc(sizeof(struct lzw_ctx)); if (lzw == NULL) { fputs("Can't allocate lzw_context.", stderr); free(read_buffer); free(decompress_buffer); return -3; } lzw_init(lzw); read_available = read_consumed = decompress_consumed = 0; while (1) { if (read_consumed >= read_available) { read_available = fread(read_buffer, 1, READ_BUFFER_SIZE, fin); read_consumed = 0; } if (decompress_consumed >= DECOMPRESS_BUFFER_SIZE || ((lzw->eos != 0 || read_available == 0) && decompress_consumed > 0)) { update_checksum(checksum, decompress_buffer, decompress_consumed); fwrite(decompress_buffer, 1, decompress_consumed, fout); copied_bytes += decompress_consumed; decompress_consumed = 0; } if (lzw->eos || read_available == 0) break; lzw_decompress(lzw, read_buffer, read_available, &read_consumed, decompress_buffer, DECOMPRESS_BUFFER_SIZE, &decompress_consumed); } if (copied_bytes != size) printf("WARNING: Decompressed image size (%lu) does not match expected (%lu)\n", copied_bytes, size); free(read_buffer); free(decompress_buffer); free(lzw); return 0; } void dsk2img(const char* filename, int no_lzw) { FILE *inf = NULL, *outf = NULL; struct dskheader header; char comment[600]; char *c, *outname = NULL; unsigned long rres; int sres; unsigned long int final_sectors; unsigned long int final_size, saved_size; long int start_offset; uint32_t checksum; int type_geom; #ifdef DEBUG fprintf(stderr, "Extracting %s\n", filename); #endif inf = fopen(filename, "rb"); if (inf == NULL) return; rres = fread(&header, sizeof(header), 1, inf); if (rres != 1) { printf("Short read\n"); goto done; } if (!valid_header(&header)) { puts("Not a valid DSK file!"); #ifdef DEBUG dump_dsk_header(&header); #endif goto done; } final_sectors = header.cylinders * header.heads * header.sectors; final_size = final_sectors * header.sectorsize; saved_size = header.imagesectors * header.sectorsize; printf("DSK format: "); switch (header.magic) { case MAGIC_DSK_OLD: puts("Old"); break; case MAGIC_DSK_UNCOMPRESSED: puts("Regular"); break; case MAGIC_DSK_COMPRESSED: puts("Compressed"); break; default: puts("Unknown"); } for (type_geom = 0; media_types[type_geom].sectors > 0; type_geom++) if ((media_types[type_geom].cylinders == header.cylinders) && (media_types[type_geom].heads == header.heads) && (media_types[type_geom].sectors == header.sectors) && (media_types[type_geom].sectorsize == header.sectorsize)) break; printf("C=%d H=%d S=%d, %lu sectors of %dB (total %luB) [%s]\n", header.cylinders, header.heads, header.sectors, final_sectors, header.sectorsize, (unsigned long)final_size, media_types[type_geom].name); sres = fseek(inf, header.commentoffset, SEEK_SET); if (sres != 0) { perror("comment seek()"); goto done; } c = fgets(comment, sizeof(comment), inf); if (c == NULL) { perror("fgets()"); goto done; } if (strlen(c) > 0) printf("comment: %s", c); start_offset = header.firstoffset ? header.firstoffset : 0x200; sres = fseek(inf, start_offset, SEEK_SET); if (sres != 0) { perror("start seek()"); goto done; } outname = guess_output_filename(filename, (header.magic == MAGIC_DSK_COMPRESSED) && (no_lzw != 0)); outf = fopen(outname, "wb+"); if (outf == NULL) { perror("fopen()"); goto done; } checksum = 0; if (header.magic != MAGIC_DSK_COMPRESSED) { if (copy_image_data(inf, outf, saved_size, &checksum) < 0) goto done; } else { if (no_lzw) { copy_compressed_image_data(inf, outf); } else { if (decompress_image_data(inf, outf, saved_size, &checksum) < 0) goto done; } } pad_file_to_size(outf, final_size); if (checksum != header.checksum) printf("ERROR: image checksum does not match. Expected 0x%08x but got 0x%08x\n", header.checksum, checksum); else puts("Done."); done: if (outf) fclose(outf); if (inf) fclose(inf); if (outname) free(outname); } void help(void) { puts("Syntax: dsk2img [-h] [-n] [FILE...]"); puts("\t-n\tDo not attempt to decompress compressed images"); puts("\t-h\tShow this help"); } int main(int argc, char* const argv[]) { int i; int no_lzw = 0; int c; while ((c = getopt(argc, argv, "hn")) != -1) switch (c) { case 'n': no_lzw = 1; break; case '?': case 'h': help(); return EXIT_SUCCESS; } if (optind >= argc) help(); else for (i = optind; i < argc; i++) /* TODO: handle DOS file globbing */ dsk2img(argv[i], no_lzw); return EXIT_SUCCESS; }