diff --git a/tools/abx/Makefile b/tools/abx/Makefile new file mode 100644 index 000000000..cc0c30ba2 --- /dev/null +++ b/tools/abx/Makefile @@ -0,0 +1,6 @@ +abx2raw: abx2raw.c abx2raw.h + gcc -W -Wall -g -O0 abx2raw.c -o abx2raw + +clean: + rm abx2raw + diff --git a/tools/abx/abx2raw.c b/tools/abx/abx2raw.c new file mode 100644 index 000000000..c92f4fe25 --- /dev/null +++ b/tools/abx/abx2raw.c @@ -0,0 +1,270 @@ +/* + * abx to raw converter. By Serge van den Boom (svdb@stack.nl), + * The actual conversion code is from Toys for Bob. + * So far, it ignores sample rates, so it will work ok as long as all + * the frames have the same frequency. This is probably + * enough for our purposes. + * + */ + +#include +#include + +#include "abx2raw.h" + +void convert_abx(FILE *in, FILE *out); +uint8_t *UnCompressAudio(struct abx_header *abx, uint8_t *source); + +int +main(int argc, char *argv[]) { + FILE *in, *out; + + if (argc != 3) { + fprintf(stderr, "abx2wav \n"); + return EXIT_FAILURE; + } + + in = fopen(argv[1], "r"); + if (!in) { + perror("Could not open input file"); + return EXIT_FAILURE; + } + + out = fopen(argv[2], "w"); + if (!out) { + perror("Could not open output file"); + return EXIT_FAILURE; + } + + convert_abx(in, out); + + fclose(in); + fclose(out); + return EXIT_SUCCESS; +} + +static signed char trans[16*16] = +{ + -8,-7,-6,-5,-4,-3,-2,-1,1,2,3,4,5,6,7,8, // Multiplier of 1 + -16,-14,-12,-10,-8,-6,-4,-2,2,4,6,8,10,12,14,16, // Multiplier of 2 + -24,-21,-18,-15,-12,-9,-6,-3,3,6,9,12,15,18,21,24, // Multiplier of 3 + -32,-28,-24,-20,-16,-12,-8,-4,4,8,12,16,20,24,28,32, // Multiplier of 4 + -40,-35,-30,-25,-20,-15,-10,-5,5,10,15,20,25,30,35,40, // Multiplier of 5 + -48,-42,-36,-30,-24,-18,-12,-6,6,12,18,24,30,36,42,48, // Multiplier of 6 + -56,-49,-42,-35,-28,-21,-14,-7,7,14,21,28,35,42,49,56, // Multiplier of 7 + -64,-56,-48,-40,-32,-24,-16,-8,8,16,24,32,40,48,56,64, // Multiplier of 8 + -72,-63,-54,-45,-36,-27,-18,-9,9,18,27,36,45,54,63,72, // Multiplier of 9 + -80,-70,-60,-50,-40,-30,-20,-10,10,20,30,40,50,60,70,80, // Multiplier of 10 + -88,-77,-66,-55,-44,-33,-22,-11,11,22,33,44,55,66,77,88, // Multiplier of 11 + -96,-84,-72,-60,-48,-36,-24,-12,12,24,36,48,60,72,84,96, // Multiplier of 12 + -104,-91,-78,-65,-52,-39,-26,-13,13,26,39,52,65,78,91,104, // Multiplier of 13 + -112,-98,-84,-70,-56,-42,-28,-14,14,28,42,56,70,84,98,112, // Multiplier of 14 + -120,-105,-90,-75,-60,-45,-30,-15,15,30,45,60,75,90,105,120,// Multiplier of 15 + -128,-112,-96,-80,-64,-48,-32,-16,16,32,48,64,80,96,112,127,// Multiplier of 16 +}; + +void +read_data(char *buf, size_t size, FILE *file) { + ssize_t numread; + + numread = fread(buf, size, 1, file); + if (numread == 0 && ferror(file)) { + perror("read header"); + exit(EXIT_FAILURE); + } + if ((size_t) numread != 1) { + fprintf(stderr, "Input file too small.\n"); + exit(EXIT_FAILURE); + } +} + +void +convert_abx(FILE *in, FILE *out) { + struct abx_header abx; + struct frame_info *frame_info; + uint8_t **frames; + uint8_t **uncoded; + int i; + + read_data((uint8_t *) &abx, sizeof (struct abx_header), in); + fprintf(stderr, "Base sample rate: %dHz\n", abx.freq); + frame_info = malloc(sizeof (struct frame_info) * abx.num_frames); + read_data((uint8_t *) frame_info, + sizeof (struct frame_info) * abx.num_frames, in); + frames = malloc(sizeof (uint8_t *) * abx.num_frames); + uncoded = malloc(sizeof (uint8_t *) * abx.num_frames); + for (i = 0; i < abx.num_frames; i++) { + frames[i] = malloc(frame_info[i].fsize); + read_data(frames[i], frame_info[i].fsize, in); + +#if 0 + // debug output to locate the first bad frame + fprintf(stderr, "Now going to process frame %d.\n", i); +#endif + +#if 0 + // skip some corrupt frames + if (i >= 270 && i <= 271) { + // fill the bad part with zeros + uncoded[i] = malloc(frame_info[i].usize); + memset(uncoded[i], '\0', frame_info[i].usize); + continue; + } +#endif + + uncoded[i] = UnCompressAudio(&abx, frames[i]); + } + + for (i = 0; i < abx.num_frames; i++) { + fwrite(uncoded[i], frame_info[i].usize, 1, out); + } + + for (i = 0; i < abx.num_frames; i++) { + free(frames[i]); + free(uncoded[i]); + } + free(uncoded); + free(frames); + free(frame_info); +} + +#define MAKE_WORD(byte1, byte2) ((byte2 << 8) | (byte1)) + +// This is used to make certain this C code is compatible when compiled on +// a 68000 based machine. (Which it has been done and tested on.) +#define Get8086word(t) MAKE_WORD ((t)[0], (t)[1]) + +// GetFreq will report the playback frequency of a particular ACOMP data +// file. +uint16_t +GetFreq(uint8_t *sound) { + return(Get8086word(sound + 2)); +} + +uint8_t * +UnCompressAudio(struct abx_header *abx, + uint8_t *source) { + uint16_t slen, frame, freq; + int16_t prev; + uint8_t *result, *dest; + + slen = Get8086word(source); + dest = result = malloc(slen * sizeof (uint8_t)); + freq = GetFreq(source); + if (freq == 0) { + freq = abx->freq; + } else if (freq != abx->freq) { + fprintf(stderr, "Frame frequency (%d) != global frequency (%d).\n", + freq, abx->freq); + fprintf(stderr, "This is not supported. Output will be corrupted.\n"); + abx->freq = freq; + } + source += 4; // Skip length, and then frequency word. + frame = *source++; // Frame size. + source += 3; // Skip sqelch value, and maximum error allowed. + prev = *source++; // Get initial previous data point. + *dest++ = prev ^ 0x80; + slen--; // Decrement total sound length. + while (slen > 0) + { + uint16_t bytes; + uint8_t sample; + + sample = *source++; // Get sample. + if (sample & RESYNC) // Is it a resync byte? + { + --slen; // Decrement output sample length. + + prev = (sample & 0x7F) << 1; // Store resync byte. + *dest++ = prev ^ 0x80; + } + else if (sample & SQLCH) // Is it a squelch byte? + { + bytes = sample & SQUELCHCNT; // And off the number of squelch bytes + slen -= bytes; // Decrement total samples remaining count. + + memset(dest, prev ^ 0x80, bytes); + dest += bytes; + } + else // Must be a delta modulate byte!! + { + int8_t *base; + + slen -= frame; // Pulling one frame out. + // Compute base address to multiplier table. + base = trans + (sample & MULTIPLIER) * 16; + switch (sample & DELTAMOD) // Delta mod resolution. + { + case ONEBIT: + { + int16_t up; + + up = base[8]; // Go up 1 bit. + for (bytes = frame / 8; bytes; bytes--) + { + uint8_t mask; + + sample = *source++; + for(mask = 0x80; mask; mask >>= 1) + { + if ( sample & mask ) + prev += up; + else + prev -= up; + if ( prev < 0 ) prev = 0; + else if ( prev > 255 ) prev = 255; + *dest++ = prev ^ 0x80; + } + } + break; + } + case TWOBIT: + base+=6; // Base address of two bit delta's. + for (bytes = frame / 4; bytes; bytes--) + { + sample = *source++; + + prev += base[sample>>6]; + if ( prev < 0 ) prev = 0; + else if ( prev > 255 ) prev = 255; + *dest++ = prev ^ 0x80; + + prev += base[(sample>>4)&0x3]; + if ( prev < 0 ) prev = 0; + else if ( prev > 255 ) prev = 255; + *dest++ = prev ^ 0x80; + + prev += base[(sample>>2)&0x3]; + if ( prev < 0 ) prev = 0; + else if ( prev > 255 ) prev = 255; + *dest++ = prev ^ 0x80; + + prev += base[sample&0x3]; + if ( prev < 0 ) prev = 0; + else if ( prev > 255 ) prev = 255; + *dest++ = prev ^ 0x80; + } + break; + case FOURBIT: + for (bytes = frame / 2; bytes; bytes--) + { + sample = *source++; + + prev += base[sample>>4]; + if ( prev < 0 ) prev = 0; + else if ( prev > 255 ) prev = 255; + *dest++ = prev ^ 0x80; + + prev += base[sample&0x0F]; + if ( prev < 0 ) prev = 0; + else if ( prev > 255 ) prev = 255; + *dest++ = prev ^ 0x80; + } + break; + } + } + // While still audio data to decompress.... + } + return result; +} + diff --git a/tools/abx/abx2raw.h b/tools/abx/abx2raw.h new file mode 100644 index 000000000..a75c3c687 --- /dev/null +++ b/tools/abx/abx2raw.h @@ -0,0 +1,27 @@ +#include + +struct abx_header { + uint16_t num_frames; + uint32_t tot_size __attribute__ ((packed)); + uint16_t bufsize __attribute__ ((packed)); + uint16_t freq __attribute__ ((packed)); +}; + +struct frame_info { + uint32_t addr; + uint16_t fsize; // compressed file size + uint16_t usize; // uncompressed file size +}; + +#define SQLCH 0x40 // Squelch byte flag +#define RESYNC 0x80 // Resync byte flag. + +#define DELTAMOD 0x30 // Delta modulation bits. + +#define ONEBIT 0x10 // One bit delta modulate +#define TWOBIT 0x20 // Two bit delta modulate +#define FOURBIT 0x30 // four bit delta modulate + +#define MULTIPLIER 0x0F // Bottom nibble contains multiplier value. +#define SQUELCHCNT 0x3F // Bits for squelching. + diff --git a/tools/abx/abx2wav.sh b/tools/abx/abx2wav.sh new file mode 100755 index 000000000..381971f52 --- /dev/null +++ b/tools/abx/abx2wav.sh @@ -0,0 +1,26 @@ +#!/bin/sh +ABX2RAW="./abx2raw" +SOX="sox" + +echo "Converting all abx files in the current directory to wav files." +echo "This script looks for abx2raw in the current dir. If it's somewhere" +echo "else, edit it to point the variable ABX2RAW to the correct location." +echo "The same goes for sox, which is expected somewhere in the path." +echo "It's just supposed to work once on a specific set of files, and hence" +echo "is pretty fragile." +echo "It is assumed that all abx files have a sample rate of 11025." +echo "If this is not the case, files won't be converted correctly." +echo "The sample rate is reported, so you can see if it goes wrong." +echo "It's also possible that the sample rate changes within one .abx file." + +echo "Press ENTER when ready." +read + +for FILE in *.abx; do + echo "File $FILE" + BASE="${FILE%%.abx}" + "$ABX2RAW" "$FILE" "${BASE}.raw" + "$SOX" -c 1 -r 11025 -b -s "${BASE}.raw" "${BASE}.wav" + echo +done + diff --git a/tools/aif/Makefile b/tools/aif/Makefile new file mode 100644 index 000000000..44ec9eaf5 --- /dev/null +++ b/tools/aif/Makefile @@ -0,0 +1,7 @@ +aif2raw: aif2raw.c aif2raw.h + gcc -W -Wall -g -O0 aif2raw.c -o aif2raw + +clean: + rm aif2raw + + diff --git a/tools/aif/aif2raw.c b/tools/aif/aif2raw.c new file mode 100644 index 000000000..40dfcfaec --- /dev/null +++ b/tools/aif/aif2raw.c @@ -0,0 +1,273 @@ +/* + * aif to raw converter. By Serge van den Boom (svdb@stack.nl), + * 20020816 + * Doesn't convert all .aif files in general, only AIFF-C, 16 bits + * SDX2-compressed. + * + */ + +#include +#include + +#include "aif2raw.h" + +void convert_aif(FILE *in, FILE *out); +void write_data(const uint8_t *data, ssize_t size, int numChannels, + FILE *out); + +int +main(int argc, char *argv[]) { + FILE *in, *out; + + if (argc != 3) { + fprintf(stderr, "aif2wav \n"); + return EXIT_FAILURE; + } + + in = fopen(argv[1], "r"); + if (!in) { + perror("Could not open input file"); + return EXIT_FAILURE; + } + + out = fopen(argv[2], "w"); + if (!out) { + perror("Could not open output file"); + return EXIT_FAILURE; + } + + convert_aif(in, out); + + fclose(in); + fclose(out); + return EXIT_SUCCESS; +} + +/* local to file byte order */ +inline uint32_t +ltof32(uint32_t val) { + return (val >> 24) | + ((val & 0x00ff0000L) >> 8) | + ((val & 0x0000ff00L) << 8) | + (val << 24); +} + +/* file to local byte order */ +inline uint32_t +ftol32(uint32_t val) { + return (val >> 24) | + ((val & 0x00ff0000L) >> 8) | + ((val & 0x0000ff00L) << 8) | + (val << 24); +} + +/* local to file byte order */ +inline uint16_t +ltof16(uint16_t val) { + return (val >> 8) | (val << 8); +} + +/* file to local byte order */ +inline uint16_t +ftol16(uint16_t val) { + return (val >> 8) | (val << 8); +} + +#if 0 +/* local to file byte order */ +inline void +ltofld(char *dest, long double val) { +} +#endif + +/* file to local byte order */ +inline long double +ftolld(const char *val) { + long double result; + ((char *) &result)[0] = val[9]; + ((char *) &result)[1] = val[8]; + ((char *) &result)[2] = val[7]; + ((char *) &result)[3] = val[6]; + ((char *) &result)[4] = val[5]; + ((char *) &result)[5] = val[4]; + ((char *) &result)[6] = val[3]; + ((char *) &result)[7] = val[2]; + ((char *) &result)[8] = val[1]; + ((char *) &result)[9] = val[0]; + ((char *) &result)[10] = 0x00; + ((char *) &result)[11] = 0x00; + return result; +} + +void +read_data(char *buf, size_t size, FILE *file) { + ssize_t numread; + + numread = fread(buf, size, 1, file); + if (numread == 0 && ferror(file)) { + perror("read header"); + exit(EXIT_FAILURE); + } + if ((size_t) numread != 1) { + fprintf(stderr, "Input file too small.\n"); + exit(EXIT_FAILURE); + } +} + +struct aifc_Chunk * +read_chunk(FILE *in) { + struct aifc_Chunk *result; + size_t numread; + + result = malloc(sizeof (struct aifc_ChunkHeader)); + numread = fread(result, sizeof (struct aifc_ChunkHeader), 1, in); + if (numread == 0) { + free(result); + if (ferror(in)) + fprintf(stderr, "Fread failed.\n"); + return NULL; + } + result = realloc(result, sizeof (struct aifc_ChunkHeader) + + ftol32(result->ckSize)); + numread = fread(&result->ckData, ftol32(result->ckSize), 1, in); + if (numread == 0) { + free(result); + if (ferror(in)) + fprintf(stderr, "Fread failed.\n"); + return NULL; + } + return result; +} + +/* number to increase number of sections by if all preallocated + sections are full */ +#define CHUNK_NUM_INC 5 + +struct aifc_Chunk ** +read_chunks(FILE *in) { + struct aifc_Chunk **chunks; + struct aifc_ContainerChunk *form; + int maxchunks; + int numchunks; + + /* The FORM chunk contains all the other chunks. */ + form = malloc(sizeof (struct aifc_ContainerChunk)); + read_data((char *) form, sizeof (struct aifc_ContainerChunk), in); + if (form->formType != aifc_FormTypeAIFC) { + fprintf(stderr, "File is not an AIFF-C file.\n"); + free(form); + exit(EXIT_FAILURE); + } + free(form); + + numchunks = 0; + maxchunks = 0; + chunks = NULL; + while (1) { + if (numchunks >= maxchunks) { + maxchunks += CHUNK_NUM_INC; + chunks = realloc(chunks, + (maxchunks + 1) * sizeof (struct aifc_Chunk *)); + } + chunks[numchunks] = read_chunk(in); + if (chunks[numchunks] == NULL) + break; + numchunks++; + } + chunks = realloc(chunks, + (numchunks + 1) * sizeof (struct aifc_Chunk *)); + return chunks; +} + +void +free_chunks(struct aifc_Chunk **chunks) { + int i; + + for (i = 0; chunks[i] != NULL; i++) + free(chunks[i]); + free(chunks); +} + +void +convert_aif(FILE *in, FILE *out) { + struct aifc_Chunk **chunks; + struct aifc_FormatVersionChunk *fverChunk; + struct aifc_ExtCommonChunk *commChunk; + struct aifc_SoundDataChunk *ssndChunk; + int i; + + fverChunk = NULL; + commChunk = NULL; + ssndChunk = NULL; + chunks = read_chunks(in); + for (i = 0; chunks[i] != NULL; i++) { + if (chunks[i]->ckID == aifc_FormVersionID && fverChunk == NULL) { + fverChunk = (struct aifc_FormatVersionChunk *) chunks[i]; + } else if (chunks[i]->ckID == aifc_CommonID && commChunk == NULL) { + commChunk = (struct aifc_ExtCommonChunk *) chunks[i]; + } else if (chunks[i]->ckID == aifc_SoundDataID && ssndChunk == NULL) { + ssndChunk = (struct aifc_SoundDataChunk *) chunks[i]; + } + } + if (fverChunk == NULL) { + fprintf(stderr, "No format version chunk found.\n"); + exit(EXIT_FAILURE); + } + if (commChunk == NULL) { + fprintf(stderr, "No common chunk found.\n"); + exit(EXIT_FAILURE); + } + if (ssndChunk == NULL) { + fprintf(stderr, "No sound data chunk found.\n"); + exit(EXIT_FAILURE); + } + fprintf(stderr, "Number of channels: %d\n", + ftol16(commChunk->numChannels)); + fprintf(stderr, "Number of sample frames: %d\n", + ftol32(commChunk->numSampleFrames)); + fprintf(stderr, "Number of bits per sample: %d\n", + ftol16(commChunk->sampleSize)); + fprintf(stderr, "Sample rate: %.2LfHz\n", + ftolld((char *) &commChunk->sampleRate)); + if (ftol16(commChunk->sampleSize) != 16) { + fprintf(stderr, "Only 16-bits files are supported.\n"); + exit(EXIT_FAILURE); + } + if (commChunk->compressionType != aifc_CompressionTypeSDX2) { + fprintf(stderr, "Only SDX2 compression is supported.\n"); + exit(EXIT_FAILURE); + } + write_data(ssndChunk->data + ftol32(ssndChunk->offset), + ftol32(ssndChunk->ckSize) - ftol32(ssndChunk->offset), + ftol16(commChunk->numChannels), out); + free_chunks(chunks); +} + +void +write_data(const uint8_t *data, ssize_t size, int numChannels, FILE *out) { + const uint8_t *srcptr, *endptr; + uint16_t *buffer, *dstptr; + int i; + int16_t *last; + + endptr = data + size; + srcptr = data; + buffer = alloca(size * sizeof (uint16_t)); + dstptr = buffer; + last = alloca(numChannels * sizeof(int16_t)); + memset(last, '\0', numChannels * sizeof(uint16_t)); + while (srcptr < endptr) { + for (i = 0; i < numChannels; i++) { + if (*srcptr & 1) { + *dstptr = last[i] + + ((*(int8_t *)srcptr * abs(*(int8_t *)srcptr)) << 1); + } else + *dstptr = (*(int8_t *)srcptr * abs(*(int8_t *)srcptr)) << 1; + last[i] = *dstptr; + dstptr++; + srcptr++; + } + } + fwrite(buffer, size * sizeof (uint16_t), 1, out); +} + diff --git a/tools/aif/aif2raw.h b/tools/aif/aif2raw.h new file mode 100644 index 000000000..97055fa83 --- /dev/null +++ b/tools/aif/aif2raw.h @@ -0,0 +1,69 @@ +#include + +typedef uint32_t aifc_ID; +typedef uint8_t aifc_Extended[10]; + /* This should actually be a 80-bits integer, but + * long double takes 96 bits, even though only 80 are used */ + +#define aifc_MAKE_ID(x1, x2, x3, x4) \ + (((x4) << 24) | ((x3) << 16) | ((x2) << 8) | (x1)) + +#define aifc_FormID aifc_MAKE_ID('F', 'O', 'R', 'M') +#define aifc_FormVersionID aifc_MAKE_ID('F', 'V', 'E', 'R') +#define aifc_CommonID aifc_MAKE_ID('C', 'O', 'M', 'M') +#define aifc_SoundDataID aifc_MAKE_ID('S', 'S', 'N', 'D') + +#define aifc_FormTypeAIFF aifc_MAKE_ID('A', 'I', 'F', 'F') +#define aifc_FormTypeAIFC aifc_MAKE_ID('A', 'I', 'F', 'C') + +#define aifc_CompressionTypeSDX2 aifc_MAKE_ID('S', 'D', 'X', '2') + +struct aifc_ChunkHeader { + aifc_ID ckID; + uint32_t ckSize; +}; + +struct aifc_Chunk { + aifc_ID ckID; /* Chunk ID */ + uint32_t ckSize; /* Chunk size, excluding header */ + uint8_t ckData[1]; /* placeholder for data */ +}; + +struct aifc_ContainerChunk { + aifc_ID ckID; /* "FORM" */ + uint32_t ckSize; /* number of bytes of data */ + aifc_ID formType; /* type of file, "AIFC" */ +}; + +struct aifc_FormatVersionChunk { + aifc_ID ckID; /* "FVER" */ + uint32_t ckSize; /* 4 */ + uint32_t timestamp; /* date of format version, in Mac format */ +}; + + +struct aifc_ExtCommonChunk { + aifc_ID ckID; /* "COMM" */ + uint32_t ckSize; /* size of chunk data */ + uint16_t numChannels; /* number of channels */ + uint32_t numSampleFrames __attribute__ ((packed)); + /* number of sample frames */ + uint16_t sampleSize __attribute__ ((packed)); + /* number of bits per sample */ + aifc_Extended sampleRate __attribute__ ((packed)); + /* number of frames per second */ + aifc_ID compressionType __attribute__ ((packed)); + /* compression type ID */ + char *compressionName; + /* compression type name */ +}; + + +struct aifc_SoundDataChunk { + aifc_ID ckID; /* "SSND" */ + uint32_t ckSize; /* size of chunk data */ + uint32_t offset; /* offset to sound data */ + uint32_t blocksize; /* size of alignment blocks */ + uint8_t data[1]; /* placeholder for data */ +}; + diff --git a/tools/aif/aif2wav.sh b/tools/aif/aif2wav.sh new file mode 100755 index 000000000..f1cf902f5 --- /dev/null +++ b/tools/aif/aif2wav.sh @@ -0,0 +1,25 @@ +#!/bin/sh +AIF2RAW="./aif2raw" +SOX="sox" + +echo "Converting all aif files in the current directory to wav files." +echo "This script looks for aif2raw in the current dir. If it's somewhere" +echo "else, edit it to point the variable AIF2RAW to the correct location." +echo "The same goes for sox, which is expected somewhere in the path." +echo "It's just supposed to work once on a specific set of files, and hence" +echo "is pretty fragile." +echo "It is assumed that all aif files have a sample rate of 11025." +echo "If this is not the case, files won't be converted correctly." +echo "The sample rate is reported, so you can see if it goes wrong." + +echo "Press ENTER when ready." +read + +for FILE in *.aif; do + echo "File $FILE" + BASE="${FILE%%.aif}" + "$AIF2RAW" "$FILE" "${BASE}.raw" + "$SOX" -c 2 -r 44100 -w -s "${BASE}.raw" "${BASE}.wav" + echo +done + diff --git a/tools/duk/Makefile b/tools/duk/Makefile new file mode 100644 index 000000000..15e3659e9 --- /dev/null +++ b/tools/duk/Makefile @@ -0,0 +1,6 @@ +unduck: unduck.c unduck.h + gcc -W -Wall -g -O0 unduck.c -o unduck + +clean: + rm unduck + diff --git a/tools/duk/unduck.c b/tools/duk/unduck.c new file mode 100644 index 000000000..911febf1a --- /dev/null +++ b/tools/duk/unduck.c @@ -0,0 +1,408 @@ +/* + * duck .duk extractor. + * By Serge van den Boom (svdb@stack.nl), 20020924 + * GPL applies + * + * duk audio is a ADPCM variant. Some code comes from MPlayer + * (http://www.mplayerhq.hu/homepage/) + * + * For now only the audio is extracted. It's saved as 11050Hz raw audio. + * + */ + +#include +#include +#include + +#include "unduck.h" + +#define AUDIO +#undef VIDEO + +void convert_duk(FILE *duk, FILE *frm, FILE *snd, FILE *dkm); +#ifdef AUDIO +void convert_audio(FILE *duk, FILE *snd, size_t cnt); +#endif +#ifdef VIDEO +void convert_video(FILE *duk, FILE *dkm, size_t cnt); +#endif + +int +main(int argc, char *argv[]) { + FILE *duk, *frm; +#ifdef AUDIO + FILE *snd; +#endif +#ifdef VIDEO + FILE *dkm; +#endif + char *filename, *filename2; + size_t filenamelen; + + if (argc != 2) { + fprintf(stderr, "unduck \n"); + return EXIT_FAILURE; + } + + filename = argv[1]; + filenamelen = strlen(filename); + if (filenamelen > 4 && + filename[filenamelen - 4] == '.' && + tolower(filename[filenamelen - 3]) == 'd' && + tolower(filename[filenamelen - 2]) == 'u' && + tolower(filename[filenamelen - 1]) == 'k') { + filename[filenamelen - 4] = '\0'; + filenamelen -= 4; + } + + filename2 = alloca(filenamelen + 5); + + sprintf(filename2, "%s.duk", filename); + duk = fopen(filename2, "rb"); + if (!duk) { + sprintf(filename2, "%s.DUK", filename); + duk = fopen(filename2, "rb"); + } + if (!duk) { + perror("Could not open .duk input file"); + return EXIT_FAILURE; + } + + sprintf(filename2, "%s.frm", filename); + frm = fopen(filename2, "rb"); + if (!frm) { + sprintf(filename2, "%s.FRM", filename); + frm = fopen(filename2, "rb"); + } + if (!frm) { + perror("Could not open .frm input file"); + return EXIT_FAILURE; + } + +#ifdef AUDIO + sprintf(filename2, "%s.raw", filename); + snd = fopen(filename2, "wb"); + if (!snd) { + perror("Could not open .raw output file"); + return EXIT_FAILURE; + } +#endif + +#ifdef VIDEO + sprintf(filename2, "%s.dkm", filename); + dkm = fopen(filename2, "wb"); + if (!dkm) { + perror("Could not open .dkm output file"); + return EXIT_FAILURE; + } +#endif + + convert_duk(duk, frm, +#ifdef AUDIO + snd, +#else + NULL, +#endif +#ifdef VIDEO + dkm +#else + NULL +#endif + ); + +#ifdef VIDEO + fclose(dkm); +#endif +#ifdef AUDIO + fclose(snd); +#endif + fclose(frm); + fclose(duk); + return EXIT_SUCCESS; +} + +/* local to file byte order */ +inline uint32_t +ltof32(uint32_t val) { + return (val >> 24) | + ((val & 0x00ff0000L) >> 8) | + ((val & 0x0000ff00L) << 8) | + (val << 24); +} + +/* file to local byte order */ +inline uint32_t +ftol32(uint32_t val) { + return (val >> 24) | + ((val & 0x00ff0000L) >> 8) | + ((val & 0x0000ff00L) << 8) | + (val << 24); +} + +/* local to file byte order */ +inline uint16_t +ltof16(uint16_t val) { + return (val >> 8) | (val << 8); +} + +/* file to local byte order */ +inline uint16_t +ftol16(uint16_t val) { + return (val >> 8) | (val << 8); +} + +int +read_data(char *buf, size_t size, FILE *file) { + ssize_t numread; + + numread = fread(buf, size, 1, file); + if (numread == 0 && ferror(file)) { + perror("read header"); + exit(EXIT_FAILURE); + } + if ((size_t) numread != 1) + return -1; // eof + return 0; +} + +void +convert_duk(FILE *duk, FILE *frm, FILE *snd, FILE *dkm) { + uint32_t framepos; + frame_header header; + + while (1) { + if (read_data((char *) &framepos, sizeof framepos, frm) == -1) { + // eof + break; + } + framepos = ftol32(framepos); + + if (fseek(duk, framepos, SEEK_SET) == -1) { + fprintf(stderr, "Cannot seek in .duk file to pos %lu", + (unsigned long) framepos); + perror(""); + exit(EXIT_FAILURE); + } + + if (read_data((char *) &header, sizeof (header), duk) == -1) { + fprintf(stderr, "Input file too small.\n"); + exit(EXIT_FAILURE); + } + +#ifdef AUDIO + convert_audio(duk, snd, ftol32(header.sndcnt)); +#else + fseek(duk, ftol32(header.sndcnt), SEEK_CUR); +#endif + fseek(duk, 16 + 2, SEEK_CUR); + // 16 bytes to skip AvlBsh header (whatever that is) + // 2 bytes for ??? (maybe they should be skipped *after* + // the audio even) +#ifdef VIDEO + convert_video(duk, dkm, ftol32(header.vidcnt)); +#else + fseek(duk, ftol32(header.vidcnt), SEEK_CUR); +#endif + } +#ifndef AUDIO + (void) snd; +#endif +#ifndef VIDEO + (void) dkm; +#endif +} + +// This table is from one of the files that came with the original 3do source +// It's slightly different from the data used by MPlayer. +static int adpcm_step[89] = { + 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xF, + 0x10, 0x12, 0x13, 0x15, 0x17, 0x1A, 0x1C, 0x1F, + 0x22, 0x26, 0x29, 0x2E, 0x32, 0x37, 0x3D, 0x43, + 0x4A, 0x51, 0x59, 0x62, 0x6C, 0x76, 0x82, 0x8F, + 0x9E, 0xAD, 0xBF, 0xD2, 0xE7, 0xFE, 0x117, 0x133, + 0x152, 0x174, 0x199, 0x1C2, 0x1EF, 0x220, 0x256, 0x292, + 0x2D4, 0x31D, 0x36C, 0x3C4, 0x424, 0x48E, 0x503, 0x583, + 0x610, 0x6AC, 0x756, 0x812, 0x8E1, 0x9C4, 0xABE, 0xBD1, + 0xCFF, 0xE4C, 0xFBA, 0x114D, 0x1308, 0x14EF, 0x1707, 0x1954, + 0x1BDD, 0x1EA6, 0x21B7, 0x2516, + 0x28CB, 0x2CDF, 0x315C, 0x364C, + 0x3BBA, 0x41B2, 0x4844, 0x4F7E, + 0x5771, 0x6030, 0x69CE, 0x7463, + 0x7FFF + }; + + +#ifdef AUDIO + +// *** BEGIN part copied from MPlayer *** +// (some little changes) + +#if 0 +// pertinent tables for IMA ADPCM +static int adpcm_step[89] = { + 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, + 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, + 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, + 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, + 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, + 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, + 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 + }; +#endif + +static int adpcm_index[16] = { + -1, -1, -1, -1, 2, 4, 6, 8, + -1, -1, -1, -1, 2, 4, 6, 8 + }; + +// clamp a number between 0 and 88 +#define CLAMP_0_TO_88(x)if (x < 0) x = 0; else if (x > 88) x = 88; + +// clamp a number within a signed 16-bit range +#define CLAMP_S16(x) \ + if (x < -32768) \ + x = -32768; \ + else if (x > 32767) \ + x = 32767; + +// sign extend a 16-bit value +#define SE_16BIT(x) \ + if (x & 0x8000) \ + x -= 0x10000; + +static void +decode_nibbles(uint16_t *output, + int output_size, int channels, + int *predictor_l, int index_l, + int *predictor_r, int index_r) { + int step[2]; + int predictor[2] = {0, 0}; + int index[2]; + int diff; + int i; + int sign; + int delta; + int channel_number = 0; + + step[0] = adpcm_step[index_l]; + step[1] = adpcm_step[index_r]; + predictor[0] = *predictor_l; + predictor[1] = *predictor_r; + index[0] = index_l; + index[1] = index_r; + + for (i = 0; i < output_size; i++) + { + delta = output[i]; + + index[channel_number] += adpcm_index[delta]; + CLAMP_0_TO_88(index[channel_number]); + + sign = delta & 8; + delta = delta & 7; + +#if 0 + // fast approximation, used in most decoders + diff = step[channel_number] >> 3; + if (delta & 4) diff += step[channel_number]; + if (delta & 2) diff += step[channel_number] >> 1; + if (delta & 1) diff += step[channel_number] >> 2; +#else + // real thing +// diff = ((signed)delta + 0.5) * step[channel_number] / 4; + diff = (((delta << 1) + 1) * step[channel_number]) >> 3; +#endif + + if (sign) + predictor[channel_number] -= diff; + else + predictor[channel_number] += diff; + + CLAMP_S16(predictor[channel_number]); + output[i] = predictor[channel_number]; + step[channel_number] = adpcm_step[index[channel_number]]; + + // toggle channel + channel_number ^= channels - 1; + } + *predictor_l = predictor[0]; + *predictor_r = predictor[1]; +} +// *** END part copied from MPlayer *** + +void +convert_audio(FILE *duk, FILE *snd, size_t cnt) { + static int predictorl = 0, + predictorr = 0; + // Important that they are static. The predictor value + // of the previous call should be reused. + int32_t indexl, indexr; + uint16_t numsamples; + uint8_t *input, *inptr, *inend; + audio_chunk *header; + uint16_t *output, *outptr; + size_t outputsize; + + input = alloca(cnt); + if (read_data((char *) input, cnt, duk) == -1) { + fprintf(stderr, "Input file too small.\n"); + exit(EXIT_FAILURE); + } + header = (audio_chunk *) input; + + if (header->magic != 0x7ff7) { + fprintf(stderr, "Error: Magic value 0xf77f not present, expected at " + "location %lu.\n", ftell(duk) - cnt + + ((char *) &header->magic - (char *) header)); + exit(EXIT_FAILURE); + } + numsamples = ftol16(header->numsamples); + if (numsamples & 3) { + fprintf(stderr, "Error: Number of samples not a multiple of 4.\n"); + exit(EXIT_FAILURE); + } + if ((ssize_t) cnt - (size_t) ((char *) &header->data - (char *) header) != + numsamples) { + fprintf(stderr, "cnt doesn't match number of samples.\n" + "Number of samples: %d\n" + "cnt: %d\n", numsamples, + cnt - (size_t) ((char *) &header->data - (char *) header)); + exit(EXIT_FAILURE); + } + + outputsize = numsamples * 2 * sizeof (uint16_t); + output = alloca(outputsize); + outptr = output; + indexl = ftol16(header->indexl); + indexr = ftol16(header->indexr); + + inptr = &header->data; + inend = input + cnt; + while (inptr < inend) { + *(outptr++) = *inptr >> 4; + *(outptr++) = *inptr & 0x0f; + inptr++; + } + decode_nibbles(output, numsamples * 2, + 2, &predictorl, indexl, &predictorr, indexr); + + fwrite(output, outputsize, 1, snd); +} + +#endif /* defined(AUDIO) */ + + +#ifdef VIDEO + +void +convert_video(FILE *duk, FILE *dkm, size_t cnt) { + fseek(duk, cnt, SEEK_CUR); + (void) duk; + (void) dkm; + (void) cnt; +} + +#endif /* defined(VIDEO) */ + diff --git a/tools/duk/unduck.h b/tools/duk/unduck.h new file mode 100644 index 000000000..2c54b233b --- /dev/null +++ b/tools/duk/unduck.h @@ -0,0 +1,17 @@ +#include + +typedef struct { + uint32_t sndcnt; + uint32_t vidcnt; +} frame_header; + +typedef struct { + uint16_t magic; // always 0xf77f + uint16_t numsamples; + uint16_t tag; // this is NOT the initial predictor as it was + // marked in some file. + uint16_t indexl; // initial index for left channel + uint16_t indexr; // initial index for right channel + uint8_t data; // placeholder for data +} audio_chunk; + diff --git a/tools/duk/unduck.sh b/tools/duk/unduck.sh new file mode 100755 index 000000000..c3de25ca7 --- /dev/null +++ b/tools/duk/unduck.sh @@ -0,0 +1,34 @@ +#!/bin/sh +UNDUCK="./unduck" +SOX="sox" + +echo "Extracting the sound data from all .duk files in the current directory." +echo "This script looks for unduck in the current dir. If it's somewhere" +echo "else, edit it to point the variable UNDUCK to the correct location." +echo "The same goes for sox, which is expected somewhere in the path." +echo "It is assumed that all sound files has a sample rate of 22050Hz" +echo "and are stereo." +echo "N.B. This program isn't fast. Make a cup of tea or something." + +echo "Press ENTER when ready." +read + +for FILE in *.{duk,DUK}; do + if [ ! -r "$FILE" ]; then + continue + fi + echo "File $FILE" + case "$FILE" in + *.duk) + BASE="${FILE%.duk}" + ;; + *.DUK) + BASE="${FILE%.DUK}" + ;; + esac + "$UNDUCK" "$FILE" + "$SOX" -c 2 -r 22050 -w -s -t raw "${BASE}.raw" -t wav "${BASE}.wav" + rm -- "${BASE}.raw" + echo +done + diff --git a/tools/pkg/Makefile b/tools/pkg/Makefile new file mode 100644 index 000000000..b2a0ab363 --- /dev/null +++ b/tools/pkg/Makefile @@ -0,0 +1,5 @@ +unpkg: unpkg.c unpkg.h + gcc -W -Wall -g -O0 unpkg.c -o unpkg + +clean: + rm unpkg diff --git a/tools/pkg/unpkg.c b/tools/pkg/unpkg.c new file mode 100644 index 000000000..d3d66d1ae --- /dev/null +++ b/tools/pkg/unpkg.c @@ -0,0 +1,241 @@ +/* + * .pkg file extractor + * By Serge van den Boom (svdb@stack.nl), 20021012 + * GPL applies + * + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "unpkg.h" + + +void readIndex(const uint8 *buf); +char *get_file_name(const uint8 *buf, const index_header *h, int package); + +int +main(int argc, char *argv[]) { + char *filename; + int in; + struct stat sb; + uint8 *buf; + + if (argc != 2) { + fprintf(stderr, "unpkg \n"); + return EXIT_FAILURE; + } + + filename = argv[1]; + + in = open(filename, O_RDONLY); + if (in == -1) { + perror("Could not open input file"); + return EXIT_FAILURE; + } + + if (fstat(in, &sb) == -1) { + perror("stat() failed"); + return EXIT_FAILURE; + } + + buf = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, in, 0); + if (buf == MAP_FAILED) { + perror("mmap() failed"); + return EXIT_FAILURE; + } + close(in); + + readIndex(buf); + + munmap(buf, sb.st_size); + + return EXIT_SUCCESS; +} + +void +readIndex(const uint8 *buf) { + index_header h; + const uint8 *bufptr; + + h.res_flags = MAKE_WORD(buf[0x00], buf[0x01]) ? 1 : 0; + fprintf(stderr, "0x00000000 File type: %s\n", + h.res_flags ? "packaged" : "not packaged"); + + h.packmem_list_offs = + MAKE_DWORD(buf[0x02], buf[0x03], buf[0x04], buf[0x05]); + fprintf(stderr, "0x00000002 packmem_list_offs: 0x%08x\n", + h.packmem_list_offs); + + h.path_list_offs = + MAKE_DWORD(buf[0x06], buf[0x07], buf[0x08], buf[0x09]); + fprintf(stderr, "0x00000006 path_list_offs: 0x%08x\n", + h.path_list_offs); + + h.file_list_offs = + MAKE_DWORD(buf[0x0a], buf[0x0b], buf[0x0c], buf[0x0d]); + fprintf(stderr, "0x0000000a file_list_offs: 0x%08x\n", + h.file_list_offs); + + h.num_packages = MAKE_WORD(buf[0x0e], buf[0x0f]); + fprintf(stderr, "0x0000000e Number of packages: %d\n", + h.num_packages); + + h.num_types = MAKE_WORD(buf[0x10], buf[0x11]); + fprintf(stderr, "0x00000010 Number of package types: %d\n", + h.num_types); + + h.header_len = MAKE_DWORD(buf[0x12], buf[0x13], buf[0x14], buf[0x15]); + fprintf(stderr, "0x00000012 Header length: 0x%08x\n", + h.header_len); + + bufptr = buf + 0x16; + h.package_list = malloc(h.num_packages * sizeof (package_desc)); + fprintf(stderr, "0x00000016 package info:\n"); + { + int i; + uint32 temp; + + for (i = 0; i < h.num_packages; i++) { + temp = MAKE_DWORD(bufptr[0], bufptr[1], bufptr[2], bufptr[3]); + h.package_list[i].num_types = GET_TYPE(temp); + h.package_list[i].num_instances = GET_INSTANCE(temp); + h.package_list[i].file_index = GET_PACKAGE(temp); + fprintf(stderr, "0x%08x #%d, %d types, %d instances, " + "file index 0x%04x, ", bufptr - buf, i, + h.package_list[i].num_types, + h.package_list[i].num_instances, + h.package_list[i].file_index); + bufptr += 4; + + temp = MAKE_DWORD(bufptr[0], bufptr[1], bufptr[2], bufptr[3]); + h.package_list[i].flags = temp >> 24; + h.package_list[i].data_loc = temp & 0x00ffffff; + + if (h.package_list[i].flags != 0xff) { + fprintf(stderr, "BAD OFFSET!\n"); + } else { + fprintf(stderr, "offset 0x%06x\n", + h.package_list[i].data_loc); + } + bufptr += 4; + } + } + + // Type info: + h.type_list = malloc(h.num_types * sizeof (type_desc)); + fprintf(stderr, "0x%08x type info:\n", bufptr - buf); + { + int i; + + for (i = 0; i < h.num_types; i++) { + h.type_list[i].instance_count = + MAKE_WORD(bufptr[0], bufptr[1]); + fprintf(stderr, "0x%08x #%d, %d instances\n", + bufptr - buf, i + 1, h.type_list[i].instance_count); + bufptr += 2; + } + } + + if ((uint32) (bufptr - buf) != h.packmem_list_offs) { + fprintf(stderr, "PACKMEM_LIST NOT IMMEDIATELY AFTER TYPE LIST!\n"); + bufptr = buf + h.packmem_list_offs; + } + fprintf(stderr, "0x%08x packmem info:\n", bufptr - buf); + { + int i, j; + int num_types, num_instances; + uint32 temp; + + for (i = 0; i < h.num_packages; i++) { + if (h.res_flags) { + // packaged + char *filename; + + filename = get_file_name(buf, &h, + h.package_list[i].file_index); + if (filename == NULL) + filename = ""; + fprintf(stderr, "0x%08x Package #%d (%s):\n", + bufptr - buf, i, filename); + } else { + // not packaged + fprintf(stderr, "0x%08x Package #%d:\n", bufptr - buf, i); + } + num_types = h.package_list[i].num_types; + for (j = 0; j < num_types; j++) { + temp = MAKE_DWORD(bufptr[0], bufptr[1], bufptr[2], bufptr[3]); + fprintf(stderr, "0x%08x Type #%d: type %d, " + "%d instances, rest: %d\n", + bufptr - buf, j, + GET_TYPE(temp), + GET_PACKAGE(temp), /* Yes, this is correct, the + package field contains the number of instances */ + GET_INSTANCE(temp)); + bufptr += 4; + } + + num_instances = h.package_list[i].num_instances; + for (j = 0; j < num_instances; j++) { + if (h.res_flags) { + // packaged + fprintf(stderr, "0x%08x Instance #%d: size %d " + "bytes\n", bufptr - buf, j, + 4 * MAKE_WORD(bufptr[0], bufptr[1])); + } else { + char *filename; + + filename = get_file_name(buf, &h, + MAKE_WORD(bufptr[0], bufptr[1])); + if (filename == NULL) + filename = ""; + fprintf(stderr, "0x%08x Instance #%d: %s\n", + bufptr - buf, j, filename); + } + bufptr += 2; + } + } + } +} + +char * +get_file_name(const uint8 *buf, const index_header *h, int file_index) { + static char result[MAXPATHLEN]; + char *ptr; + file_info *fi; + uint16 path_offset; + + ptr = result; + fi = &((file_info *) (buf + h->file_list_offs))[file_index]; + if (fi->filename[0] == '\0') + return NULL; + + path_offset = MAKE_WORD(fi->path_offset[0], fi->path_offset[1]); + if (path_offset != 0xffff) { + strcpy(ptr, buf + h->path_list_offs + path_offset); + ptr += strlen(ptr); + *ptr = '/'; + ptr++; + } + + ptr[8] = '\0'; + strncpy(ptr, fi->filename, 8); + ptr += strlen(ptr); + + *ptr = '.'; + ptr++; + + ptr[3] = '\0'; + strncpy(ptr, fi->extension, 3); + + return result; +} + diff --git a/tools/pkg/unpkg.h b/tools/pkg/unpkg.h new file mode 100644 index 000000000..126522689 --- /dev/null +++ b/tools/pkg/unpkg.h @@ -0,0 +1,64 @@ +#include + +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef unsigned int uint32; + +#define MAKE_WORD(x1, x2) (((x2) << 8) | (x1)) +#define MAKE_DWORD(x1, x2, x3, x4) (((x4) << 24) | ((x3) << 16) | \ + ((x2) << 8) | (x1)) + +#define TYPE_BITS 8 +#define INSTANCE_BITS 13 +#define PACKAGE_BITS 11 + +#define GET_TYPE(res) \ + ((uint8)(res) & (uint8)((1 << TYPE_BITS) - 1)) +#define GET_INSTANCE(res) \ + ((uint16)((res) >> TYPE_BITS) & ((1 << INSTANCE_BITS) - 1)) +#define GET_PACKAGE(res) \ + ((uint16)((res) >> (TYPE_BITS + INSTANCE_BITS)) & \ + ((1 << PACKAGE_BITS) - 1)) + +typedef struct +{ + uint16 instance_count; +#if 0 + RES_VECTORS func_vectors; +#endif +} type_desc; + +typedef struct { + uint8 path_offset[2]; + char filename[8]; + char extension[3]; +} file_info; + +typedef struct { + uint8 num_types; + uint16 num_instances; // only low 13 bits used + uint16 file_index; // only low 11 bits used + uint8 flags; + uint32 data_loc; // only low 24 bits used +} package_desc; + +typedef struct { + uint32 packmem_list_offs; + uint32 path_list_offs; + uint32 file_list_offs; + uint16 num_packages; + uint8 num_types; + uint8 res_flags; + uint16 header_len; + package_desc *package_list; + type_desc *type_list; + +#if 0 +#ifndef PACKAGING + char index_file_name[36]; + DWORD data_offs; + MEM_HANDLE hPredHeader, hSuccHeader; +#endif /* PACKAGING */ +#endif +} index_header; +