488 lines
15 KiB
C
488 lines
15 KiB
C
#define _POSIX_C_SOURCE 200809L
|
|
#include "lzw.h"
|
|
#include "utils.h"
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#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 <io.h> /* 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;
|
|
}
|