Initial commit
This commit is contained in:
commit
74d954ac21
|
@ -0,0 +1,2 @@
|
|||
---
|
||||
BasedOnStyle: Webkit
|
|
@ -0,0 +1,8 @@
|
|||
*~
|
||||
*%
|
||||
*.o
|
||||
*.obj
|
||||
*.exe
|
||||
dsk2img
|
||||
test-lzw
|
||||
.vscode/
|
|
@ -0,0 +1,23 @@
|
|||
CC = clang
|
||||
CFLAGS ?= -Wall -pedantic -std=c89 -O0 -g
|
||||
STRIP = strip
|
||||
FORMAT = clang-format -i
|
||||
BIN = dsk2img
|
||||
TESTS = test-lzw
|
||||
|
||||
.PHONY: all strip clean format
|
||||
|
||||
all: $(BIN)
|
||||
tests: $(TESTS)
|
||||
|
||||
strip: $(BIN)
|
||||
$(STRIP) $^
|
||||
|
||||
test-lzw: lzw.o
|
||||
dsk2img: lzw.o
|
||||
|
||||
clean:
|
||||
$(RM) $(BIN) $(TESTS) *.o *~ *%
|
||||
|
||||
format:
|
||||
$(FORMAT) *.c
|
|
@ -0,0 +1,13 @@
|
|||
#!/bin/bash
|
||||
|
||||
export WATCOM=/opt/ow
|
||||
PATH="$WATCOM/binl:$PATH"
|
||||
|
||||
CFLAGS=(-Wall -Wextra -std=c89 -O3 -g0 -s)
|
||||
CFILES=(dsk2img.c lzw.c)
|
||||
|
||||
set -x
|
||||
|
||||
owcc -o dsk2img.exe "${CFLAGS[@]}" -march=i86 -bdos -mcmodel=s "${CFILES[@]}"
|
||||
owcc -o dsk2img-win32.exe "${CFLAGS[@]}" -march=i386 -mtune=i386 -bnt "${CFILES[@]}"
|
||||
owcc -o dsk2img-os232.exe "${CFLAGS[@]}" -march=i386 -mtune=i386 -bos2v2 "${CFILES[@]}"
|
|
@ -0,0 +1,437 @@
|
|||
#define _POSIX_C_SOURCE 2
|
||||
#include "lzw.h"
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#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 '\\'
|
||||
#else
|
||||
#undef DOSLIKE
|
||||
#define DIRSEP '/'
|
||||
#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, 1, 8, "5.25\" SSDD 160KB" },
|
||||
{ 512, 40, 1, 9, "5.25\" SSDD 180KB" },
|
||||
{ 512, 40, 2, 8, "5.25\" DSDD 320KB" },
|
||||
{ 512, 40, 2, 9, "5.25\" DSDD 360KB" },
|
||||
{ 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;
|
||||
|
||||
/* NOLINTBEGIN(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) */
|
||||
ifnlen = strlen(input_filename);
|
||||
ofnlen = ifnlen + 9;
|
||||
outname = (char*)malloc(ofnlen);
|
||||
strncpy(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 */
|
||||
strcat(outname, ".lzw");
|
||||
else
|
||||
strcat(outname, ".img");
|
||||
#else
|
||||
strncat(outname, ".img", ofnlen);
|
||||
if (is_compressed)
|
||||
strncat(outname, ".lzw", ofnlen);
|
||||
#endif
|
||||
/* NOLINTEND(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) */
|
||||
|
||||
return outname;
|
||||
}
|
||||
|
||||
void pad_file_to_size(FILE* f, unsigned long size)
|
||||
{
|
||||
long r;
|
||||
unsigned long current_pos;
|
||||
|
||||
r = ftell(f);
|
||||
if (r < 0) {
|
||||
perror("ftell()");
|
||||
}
|
||||
current_pos = r;
|
||||
if (current_pos > size)
|
||||
printf("ERROR: output file is larger than expected! (%lu vs %lu)\n", current_pos, size);
|
||||
if (current_pos < size - 1) {
|
||||
long pos;
|
||||
|
||||
pos = ((long)size) - 1;
|
||||
fseek(f, pos, SEEK_SET);
|
||||
}
|
||||
if (current_pos < size)
|
||||
fputc(0, f);
|
||||
}
|
||||
|
||||
int valid_header(const struct dskheader* h)
|
||||
{
|
||||
unsigned int final_sectors;
|
||||
unsigned long int 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;
|
||||
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 + (h->imageclusters << h->clustershift) != 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;
|
||||
}
|
||||
|
||||
#if 0
|
||||
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 dsk2img(const char* filename, int no_lzw)
|
||||
{
|
||||
FILE *inf, *outf;
|
||||
struct dskheader header;
|
||||
char comment[600];
|
||||
char *c, *outname;
|
||||
unsigned long rres;
|
||||
int sres;
|
||||
unsigned long int final_sectors, image_sectors, sector;
|
||||
unsigned long int final_size;
|
||||
long int start_offset;
|
||||
uint8_t* buffer;
|
||||
uint32_t checksum;
|
||||
int i;
|
||||
int type_geom;
|
||||
char *rbuf = NULL, *wbuf = NULL;
|
||||
|
||||
inf = fopen(filename, "rb");
|
||||
if (inf == NULL)
|
||||
return;
|
||||
|
||||
rbuf = (char*)malloc(4096);
|
||||
if (rbuf)
|
||||
setvbuf(inf, rbuf, _IOFBF, 4096);
|
||||
else
|
||||
printf("WARNING: can't allocate read buffer.");
|
||||
|
||||
rres = fread(&header, sizeof(header), 1, inf);
|
||||
if (rres != 1) {
|
||||
printf("Short read\n");
|
||||
fclose(inf);
|
||||
if (rbuf)
|
||||
free(rbuf);
|
||||
return;
|
||||
}
|
||||
|
||||
/* dump_dsk_header(&header); */
|
||||
|
||||
if (!valid_header(&header)) {
|
||||
fclose(inf);
|
||||
puts("Not a valid DSK file!");
|
||||
if (rbuf)
|
||||
free(rbuf);
|
||||
return;
|
||||
}
|
||||
|
||||
final_sectors = header.cylinders * header.heads * header.sectors;
|
||||
final_size = final_sectors * 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()");
|
||||
fclose(inf);
|
||||
if (rbuf)
|
||||
free(rbuf);
|
||||
return;
|
||||
}
|
||||
|
||||
c = fgets(comment, sizeof(comment), inf);
|
||||
if (c == NULL) {
|
||||
perror("fgets()");
|
||||
fclose(inf);
|
||||
if (rbuf)
|
||||
free(rbuf);
|
||||
return;
|
||||
}
|
||||
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()");
|
||||
fclose(inf);
|
||||
if (rbuf)
|
||||
free(rbuf);
|
||||
return;
|
||||
}
|
||||
|
||||
outname = guess_output_filename(filename, (header.magic == MAGIC_DSK_COMPRESSED) && (no_lzw != 0));
|
||||
|
||||
outf = fopen(outname, "wb+");
|
||||
if (outf == NULL) {
|
||||
perror("fopen()");
|
||||
fclose(inf);
|
||||
free(outname);
|
||||
if (rbuf)
|
||||
free(rbuf);
|
||||
return;
|
||||
}
|
||||
|
||||
wbuf = (char*)malloc(4096);
|
||||
if (wbuf)
|
||||
setvbuf(outf, wbuf, _IOFBF, 4096);
|
||||
else
|
||||
printf("WARNING: can't allocate write buffer.");
|
||||
|
||||
buffer = (uint8_t*)malloc(header.sectorsize);
|
||||
if (buffer == NULL) {
|
||||
perror("malloc()");
|
||||
fclose(outf);
|
||||
fclose(inf);
|
||||
free(outname);
|
||||
if (rbuf)
|
||||
free(rbuf);
|
||||
if (wbuf)
|
||||
free(wbuf);
|
||||
return;
|
||||
}
|
||||
|
||||
/* image_sectors = (header.imageclusters << header.clustershift) +
|
||||
header.firstclustersector - 1; */
|
||||
image_sectors = header.imagesectors;
|
||||
checksum = 0;
|
||||
if (header.magic != MAGIC_DSK_COMPRESSED) {
|
||||
for (sector = 0; sector < image_sectors; sector++) {
|
||||
rres = fread(buffer, header.sectorsize, 1, inf);
|
||||
if (rres != 1) {
|
||||
perror("fread()");
|
||||
fclose(outf);
|
||||
fclose(inf);
|
||||
free(buffer);
|
||||
free(outname);
|
||||
if (rbuf)
|
||||
free(rbuf);
|
||||
if (wbuf)
|
||||
free(wbuf);
|
||||
return;
|
||||
}
|
||||
for (i = 0; i < header.sectorsize / 2; i++)
|
||||
checksum += buffer[i * 2] + 256 * buffer[i * 2 + 1];
|
||||
rres = fwrite(buffer, header.sectorsize, 1, outf);
|
||||
if (rres != 1) {
|
||||
perror("fwrite()");
|
||||
fclose(outf);
|
||||
fclose(inf);
|
||||
free(buffer);
|
||||
free(outname);
|
||||
if (rbuf)
|
||||
free(rbuf);
|
||||
if (wbuf)
|
||||
free(wbuf);
|
||||
return;
|
||||
}
|
||||
}
|
||||
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.");
|
||||
} else {
|
||||
if (no_lzw) {
|
||||
size_t wres;
|
||||
|
||||
for (sector = 0;; sector++) {
|
||||
rres = fread(buffer, 1, header.sectorsize, inf);
|
||||
for (i = 0; i < rres; i++)
|
||||
checksum += buffer[i];
|
||||
wres = fwrite(buffer, 1, rres, outf);
|
||||
if (wres != rres) {
|
||||
perror("fwrite()");
|
||||
fclose(outf);
|
||||
fclose(inf);
|
||||
free(buffer);
|
||||
free(outname);
|
||||
if (rbuf)
|
||||
free(rbuf);
|
||||
if (wbuf)
|
||||
free(wbuf);
|
||||
return;
|
||||
}
|
||||
if (rres < header.sectorsize)
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
/* TODO: checksum */
|
||||
lzw_decompress_file(inf, outf);
|
||||
pad_file_to_size(outf, final_size);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(outf);
|
||||
fclose(inf);
|
||||
free(buffer);
|
||||
free(outname);
|
||||
if (rbuf)
|
||||
free(rbuf);
|
||||
if (wbuf)
|
||||
free(wbuf);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
* LZW algorithm as used in the IBM .DSK floppy disk image format.
|
||||
* Uses a fixed 12bit code size and an LRU dictionary entry replacement policy.
|
||||
*/
|
||||
|
||||
#include "lzw.h"
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
void lzw_init(struct lzw_ctx* c)
|
||||
{
|
||||
int i;
|
||||
|
||||
c->coffset = c->doffset = c->codes = 0L;
|
||||
c->input_buffer = 0;
|
||||
c->dict[0].usecount = c->dict[0].length = 0;
|
||||
c->dict[0].prefix = c->dict[0].lru_prev = c->dict[0].lru_next = 0;
|
||||
for (i = 1; i <= 256; i++) {
|
||||
c->dict[i].length = 1;
|
||||
c->dict[i].prefix = 0;
|
||||
c->dict[i].last = i - 1;
|
||||
c->dict[i].first = i - 1;
|
||||
c->dict[i].usecount = 1; /* so that this code will never end up in the lru list */
|
||||
c->dict[i].lru_prev = c->dict[i].lru_next = 0;
|
||||
}
|
||||
for (i = 257; i < 4096; i++) {
|
||||
c->dict[i].length = 0;
|
||||
c->dict[i].prefix = 0;
|
||||
c->dict[i].last = 0;
|
||||
c->dict[i].first = 0;
|
||||
c->dict[i].usecount = 0;
|
||||
c->dict[i].lru_prev = (i == 257 ? 0 : i - 1);
|
||||
c->dict[i].lru_next = (i == 4095 ? 0 : i + 1);
|
||||
}
|
||||
c->lru_head = 257;
|
||||
c->lru_tail = 4095;
|
||||
c->last_emitted_code = 0;
|
||||
}
|
||||
|
||||
/* Remove code from the lru list */
|
||||
static void lzw_lru_unlink(struct lzw_ctx* c, uint16_t code)
|
||||
{
|
||||
uint16_t next, prev;
|
||||
|
||||
next = c->dict[code].lru_next;
|
||||
prev = c->dict[code].lru_prev;
|
||||
if (prev != 0)
|
||||
c->dict[prev].lru_next = next;
|
||||
else
|
||||
c->lru_head = next;
|
||||
if (next != 0)
|
||||
c->dict[next].lru_prev = prev;
|
||||
else
|
||||
c->lru_tail = prev;
|
||||
}
|
||||
|
||||
/* Add the given code to the back of the lru list. The code must be removed from the list before calling this function. */
|
||||
static void lzw_lru_append(struct lzw_ctx* c, uint16_t code)
|
||||
{
|
||||
c->dict[code].lru_next = 0;
|
||||
c->dict[code].lru_prev = c->lru_tail;
|
||||
c->dict[c->lru_tail].lru_next = code;
|
||||
c->lru_tail = code;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static void lzw_print_lru(struct lzw_ctx* c)
|
||||
{
|
||||
printf("head=%03x tail=%03x\n", c->lru_head, c->lru_tail);
|
||||
for (int i = 0; i < 4096; i++)
|
||||
printf("[%03x] next=%03x prev=%03x\n", i, c->dict[i].lru_next,
|
||||
c->dict[i].lru_prev);
|
||||
}
|
||||
|
||||
static void lzw_validate_lru(struct lzw_ctx* c)
|
||||
{
|
||||
for (uint16_t code = c->lru_head; code != 0; code = c->dict[code].lru_next) {
|
||||
printf("%03x", code);
|
||||
if (c->dict[code].usecount != 0)
|
||||
putchar('!');
|
||||
if (c->dict[code].lru_next != 0 && c->dict[c->dict[code].lru_next].lru_prev != code)
|
||||
putchar('>');
|
||||
putchar(' ');
|
||||
}
|
||||
putchar('\n');
|
||||
}
|
||||
#endif
|
||||
|
||||
static int lzw_build_entry(struct lzw_ctx* c, uint16_t code)
|
||||
{
|
||||
int pos;
|
||||
|
||||
pos = c->lru_head;
|
||||
lzw_lru_unlink(c, pos);
|
||||
|
||||
if (c->dict[pos].prefix != 0) {
|
||||
c->dict[c->dict[pos].prefix].usecount--;
|
||||
if (c->dict[c->dict[pos].prefix].usecount == 0)
|
||||
lzw_lru_append(c, c->dict[pos].prefix);
|
||||
}
|
||||
|
||||
c->dict[pos].prefix = c->last_emitted_code;
|
||||
c->dict[pos].last = c->dict[(code == pos ? c->dict[pos].prefix : code)].first;
|
||||
c->dict[pos].first = c->dict[c->dict[pos].prefix].first;
|
||||
c->dict[pos].length = c->dict[c->dict[pos].prefix].length + 1;
|
||||
c->dict[pos].usecount = 0;
|
||||
|
||||
if (c->dict[c->dict[pos].prefix].usecount == 0)
|
||||
lzw_lru_unlink(c, c->dict[pos].prefix);
|
||||
c->dict[c->dict[pos].prefix].usecount++;
|
||||
lzw_lru_append(c, pos);
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
static void lzw_emit(struct lzw_ctx* c, uint16_t code, FILE* f)
|
||||
{
|
||||
int i;
|
||||
uint16_t t;
|
||||
|
||||
for (i = c->dict[code].length, t = code; t != 0 && i > 0; i--, t = c->dict[t].prefix)
|
||||
c->output_buffer[i - 1] = c->dict[t].last;
|
||||
fwrite(c->output_buffer, c->dict[code].length, 1, f);
|
||||
|
||||
c->doffset += c->dict[code].length;
|
||||
c->last_emitted_code = code;
|
||||
}
|
||||
|
||||
static int lzw_get_next_code(struct lzw_ctx* c, FILE* f)
|
||||
{
|
||||
uint16_t result;
|
||||
int next_byte;
|
||||
|
||||
next_byte = fgetc(f);
|
||||
if (next_byte == EOF)
|
||||
return -1;
|
||||
c->coffset++;
|
||||
|
||||
if ((c->codes & 1) == 0) {
|
||||
c->input_buffer = fgetc(f);
|
||||
if (c->input_buffer == EOF)
|
||||
return -1;
|
||||
c->coffset++;
|
||||
result = (next_byte << 4) | (c->input_buffer >> 4);
|
||||
} else {
|
||||
result = ((c->input_buffer & 0x0f) << 8) | next_byte;
|
||||
}
|
||||
|
||||
c->codes++;
|
||||
return result & 0x0fff;
|
||||
}
|
||||
|
||||
int lzw_decompress_file(FILE* infp, FILE* outfp)
|
||||
{
|
||||
struct lzw_ctx* ctx;
|
||||
int code;
|
||||
|
||||
ctx = (struct lzw_ctx*)malloc(sizeof(struct lzw_ctx));
|
||||
if (ctx == NULL)
|
||||
return -1;
|
||||
|
||||
lzw_init(ctx);
|
||||
|
||||
while ((code = lzw_get_next_code(ctx, infp)) != 0) {
|
||||
if (ctx->last_emitted_code != 0)
|
||||
lzw_build_entry(ctx, code);
|
||||
lzw_emit(ctx, code, outfp);
|
||||
}
|
||||
|
||||
/*
|
||||
printf(
|
||||
"\nEnd of file after reading %ld bytes (%ld symbols) and writing %ld "
|
||||
"bytes\n",
|
||||
ctx.coffset, ctx.codes, ctx.doffset);
|
||||
*/
|
||||
free(ctx);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
#ifndef LZW_H_
|
||||
#define LZW_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
struct lzw_dict_entry {
|
||||
uint16_t prefix; /* Code containing the first length-1 bytes in this code */
|
||||
uint16_t length; /* Length of this code */
|
||||
uint16_t usecount; /* How many codes have this code as a prefix */
|
||||
uint16_t lru_prev; /* Previous entry in the lru list */
|
||||
uint16_t lru_next; /* Next entry in the lru list */
|
||||
uint8_t last; /* Last byte in this code */
|
||||
uint8_t first; /* First byte in this code */
|
||||
};
|
||||
|
||||
struct lzw_ctx {
|
||||
struct lzw_dict_entry dict[4096]; /* Dictionary entries */
|
||||
uint8_t output_buffer[4096]; /* Temporary buffer used to collect the byte string corresponding to a code */
|
||||
size_t coffset; /* Position in the compressed stream */
|
||||
size_t doffset; /* Position in the decompressed stream */
|
||||
size_t codes; /* Number of codes processed so far */
|
||||
int input_buffer; /* Temporary buffer used in the extraction of 12bit codes from the compressed stream */
|
||||
uint16_t lru_head; /* Index of the first entry in the lru list */
|
||||
uint16_t lru_tail; /* Index of the last entry in the lru list */
|
||||
uint16_t last_emitted_code; /* Code emitted in the previous round */
|
||||
};
|
||||
|
||||
void lzw_init(struct lzw_ctx* c);
|
||||
int lzw_decompress_file(FILE* infp, FILE* outfp);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,59 @@
|
|||
#include "lzw.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
int decompress(char* filename)
|
||||
{
|
||||
char* outfilename;
|
||||
FILE *fp, *outfp;
|
||||
int i;
|
||||
unsigned int ifnlen, ofnlen;
|
||||
|
||||
fp = fopen(filename, "rb");
|
||||
if (fp == NULL) {
|
||||
perror("fopen()");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* NOLINTBEGIN(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) */
|
||||
ifnlen = strlen(filename);
|
||||
ofnlen = ifnlen + 5;
|
||||
outfilename = (char*)malloc(ofnlen);
|
||||
strncpy(outfilename, filename, ofnlen);
|
||||
for (i = ifnlen - 1; i > 0 && outfilename[i] != '/'; i--)
|
||||
if (outfilename[i] == '.') {
|
||||
outfilename[i] = '\0';
|
||||
break;
|
||||
}
|
||||
if (strcmp(filename, outfilename) == 0)
|
||||
strncat(outfilename, ".out", ofnlen);
|
||||
/* NOLINTEND(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) */
|
||||
|
||||
printf("Writing to %s\n", outfilename);
|
||||
outfp = fopen(outfilename, "wb");
|
||||
if (outfp == NULL) {
|
||||
perror("fopen()");
|
||||
free(outfilename);
|
||||
return -1;
|
||||
}
|
||||
|
||||
lzw_decompress_file(fp, outfp);
|
||||
free(outfilename);
|
||||
fclose(outfp);
|
||||
fclose(fp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 1; i < argc; i++) {
|
||||
printf("Decompressing %s\n", argv[i]);
|
||||
decompress(argv[i]);
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
Loading…
Reference in New Issue