diff --git a/Makefile b/Makefile index d036771..2663f9f 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,8 @@ OBJ=\ $(BUILDDIR)/id3.o \ $(BUILDDIR)/midi.o \ $(BUILDDIR)/mod.o \ - $(BUILDDIR)/s3m.o + $(BUILDDIR)/s3m.o \ + $(BUILDDIR)/it.o CC=gcc CFLAGS=-Wall -std=gnu99 -O2 -fmessage-length=0 -g BIN=$(BUILDDIR)/audioextract @@ -20,7 +21,7 @@ all: $(BIN) $(BIN): $(OBJ) $(CC) $(CFLAGS) $(OBJ) -o $@ -$(BUILDDIR)/audioextract.o: audioextract.c audioextract.h ogg.h wave.h mpeg.h id3.h midi.h mod.h s3m.h +$(BUILDDIR)/audioextract.o: audioextract.c audioextract.h ogg.h wave.h mpeg.h id3.h midi.h mod.h s3m.h it.h $(CC) $(CFLAGS) $< -o $@ -c $(BUILDDIR)/wave.o: wave.c audioextract.h wave.h @@ -44,6 +45,9 @@ $(BUILDDIR)/mod.o: mod.c audioextract.h mod.h $(BUILDDIR)/s3m.o: s3m.c audioextract.h s3m.h $(CC) $(CFLAGS) $< -o $@ -c +$(BUILDDIR)/it.o: it.c audioextract.h it.h + $(CC) $(CFLAGS) $< -o $@ -c + install: all install -s -D $(BIN) "$(PREFIX)/bin/audioextract" diff --git a/README.md b/README.md index b6f6615..c06a0c6 100644 --- a/README.md +++ b/README.md @@ -38,11 +38,14 @@ using the `--min-size` one can hopefully extract only real MPEG files. default the default set of formats (AIFF, ID3v2, Ogg, RIFF, MIDI, MOD) aiff big-endian (Apple) wave files id3v2 MPEG files with ID3v2 tags at the start + it ImpulseTracker files midi MIDI files - mod MOD files + mod FastTracker files mpeg any MPEG files (e.g. MP3) ogg Ogg files (Vorbis, FLAC, Opus, Theora, etc.) riff little-endian (Windows) wave files + s3m ScreamTracker files + tracker all tracker files (MOD, S3M, IT) wave both RIFF and AIFF wave files WARNING: Because MPEG files do not have a nice file magic, using @@ -57,7 +60,7 @@ using the `--min-size` one can hopefully extract only real MPEG files. removed from the set of formats to extract. E.g. extract everything except wave files: - ./build/audioextract --formats=all,-wave data.bin + audioextract --formats=all,-wave data.bin -o, --output=DIR Directory where extracted files should be written. (default: ".") -m, --min-size=SIZE Minumum size of extracted files (skip smaller). (default: 0) diff --git a/audioextract.c b/audioextract.c index 884b5eb..dbc2834 100644 --- a/audioextract.c +++ b/audioextract.c @@ -19,6 +19,7 @@ #include "midi.h" #include "mod.h" #include "s3m.h" +#include "it.h" enum fileformat { NONE = 0, @@ -30,13 +31,14 @@ enum fileformat { MIDI = 32, MOD = 64, S3M = 128, + IT = 256, // TODO: -// IT = 256, // XM = 512, }; -#define ALL_FORMATS (OGG | RIFF | AIFF | MPEG | ID3v2 | MIDI | MOD | S3M) -#define DEFAULT_FORMATS (OGG | RIFF | AIFF | ID3v2 | MIDI | MOD | S3M) +#define ALL_FORMATS (OGG | RIFF | AIFF | MPEG | ID3v2 | MIDI | MOD | S3M | IT) +#define DEFAULT_FORMATS (OGG | RIFF | AIFF | ID3v2 | MIDI | MOD | S3M | IT) +#define TRACKER_FORMATS (MOD | S3M | IT) int usage(int argc, char **argv) { @@ -56,11 +58,14 @@ int usage(int argc, char **argv) " default the default set of formats (AIFF, ID3v2, Ogg, RIFF, MIDI, MOD)\n" " aiff big-endian (Apple) wave files\n" " id3v2 MPEG files with ID3v2 tags at the start\n" + " it ImpulseTracker files\n" " midi MIDI files\n" - " mod MOD files\n" + " mod FastTracker files\n" " mpeg any MPEG files (e.g. MP3)\n" " ogg Ogg files (Vorbis, FLAC, Opus, Theora, etc.)\n" " riff little-endian (Windows) wave files\n" + " s3m ScreamTracker files\n" + " tracker all tracker files (MOD, S3M, IT)\n" " wave both RIFF and AIFF wave files\n" "\n" " WARNING: Because MPEG files do not have a nice file magic, using\n" @@ -128,6 +133,11 @@ const unsigned char *findmagic(const unsigned char *start, const unsigned char * *format = ID3v2; return start; } + else if (formats & IT && magic == IT_MAGIC) + { + *format = IT; + return start; + } else if (formats & MPEG && IS_MPEG_MAGIC(start)) { *format = MPEG; @@ -388,6 +398,15 @@ int extract(const char *filepath, const char *outdir, size_t minsize, size_t max else ++ ptr; break; + case IT: + if (it_isfile(ptr, end, &length)) + { + WRITE_FILE(ptr, length, "it"); + ptr += length; + } + else ++ ptr; + break; + case NONE: ++ ptr; break; @@ -472,6 +491,14 @@ int parse_formats(const char *formats) { mask = S3M; } + else if (strncasecmp("it", start, len) == 0) + { + mask = IT; + } + else if (strncasecmp("tracker", start, len) == 0) + { + mask = TRACKER_FORMATS; + } else if (strncasecmp("all", start, len) == 0) { mask = ALL_FORMATS; diff --git a/it.c b/it.c new file mode 100644 index 0000000..8e6e975 --- /dev/null +++ b/it.c @@ -0,0 +1,90 @@ +#include "it.h" + +int it_isfile(const unsigned char *start, const unsigned char *end, size_t *lengthptr) +{ + size_t input_len = (size_t)(end - start); + if (input_len < IT_HEADER_SIZE) + return 0; + + if (MAGIC(start) != IT_MAGIC) + return 0; + + uint16_t orders = le16toh(*(uint16_t *)(start + 0x20)); + uint16_t instruments = le16toh(*(uint16_t *)(start + 0x22)); + uint16_t samples = le16toh(*(uint16_t *)(start + 0x24)); + uint16_t patterns = le16toh(*(uint16_t *)(start + 0x26)); + + size_t length = IT_HEADER_SIZE + orders + instruments * 4 + samples * 4 + patterns * 4; + + if (input_len < length) + return 0; + +#define UPDATE_LENGTH(len) \ + { \ + size_t _len = (len); \ + if (_len > length) \ + { \ + length = _len; \ + if (input_len < length) \ + return 0; \ + } \ + } + + /* scan instruments */ + for (const uint32_t *para = (const uint32_t *)(start + IT_HEADER_SIZE + orders), + *para_end = para + instruments; + para < para_end; ++ para) + { + size_t off = (size_t)le32toh(*para); + + UPDATE_LENGTH(off + IT_INSTRUMENT_SIZE); + } + + /* scan samples */ + for (const uint32_t *para = (const uint32_t *)(start + IT_HEADER_SIZE + orders + instruments * 4), + *para_end = para + samples; + para < para_end; ++ para) + { + size_t off = (size_t)le32toh(*para); + const unsigned char *ptr = start + off; + + UPDATE_LENGTH(off + IT_SAMPLE_HEADER_SIZE); + + if (MAGIC(ptr) != IT_SAMPLE_MAGIC) + continue; + + uint32_t sample_length = le32toh(*(uint32_t *)(ptr + 0x30)); + uint32_t sample_pointer = le32toh(*(uint32_t *)(ptr + 0x48)); + + if (sample_length && sample_pointer) + { + size_t sample_end = sample_pointer + sample_length; + + // there are some IT files out there with truncated samples: + if (sample_end > input_len) sample_end = input_len; + if (sample_end > length) length = sample_end; + } + } + + /* scan patterns */ + for (const uint32_t *para = (const uint32_t *)(start + IT_HEADER_SIZE + orders + instruments * 4 + samples * 4), + *para_end = para + patterns; + para < para_end; ++ para) + { + size_t off = (size_t)le32toh(*para); + const unsigned char *ptr = start + off; + + UPDATE_LENGTH(off + IT_PATTERN_HEADER_SIZE); + + uint16_t pattern_length = le32toh(*(uint16_t *)ptr); + size_t pattern_end = off + IT_PATTERN_HEADER_SIZE + pattern_length; + + // there are some IT files out there with truncated patterns: + if (pattern_end > input_len) pattern_end = input_len; + if (pattern_end > length) length = pattern_end; + } + + if (lengthptr) *lengthptr = length; + + return 1; +} diff --git a/it.h b/it.h new file mode 100644 index 0000000..b0703d5 --- /dev/null +++ b/it.h @@ -0,0 +1,16 @@ +#ifndef AUDIOEXTRACT_IT_H__ +#define AUDIOEXTRACT_IT_H__ + +#include "audioextract.h" + +#define IT_MAGIC MAGIC("IMPM") +#define IT_INSTRUMENT_MAGIC MAGIC("IMPI") +#define IT_SAMPLE_MAGIC MAGIC("IMPS") +#define IT_HEADER_SIZE 192 +#define IT_INSTRUMENT_SIZE 554 +#define IT_SAMPLE_HEADER_SIZE 80 +#define IT_PATTERN_HEADER_SIZE 4 + +int it_isfile(const unsigned char *start, const unsigned char *end, size_t *lengthptr); + +#endif /* AUDIOEXTRACT_IT_H__ */