diff --git a/Makefile b/Makefile index 6a4f990..291aa83 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,13 @@ PREFIX=/usr/local BUILDDIR=build -OBJ=$(BUILDDIR)/audioextract.o $(BUILDDIR)/wave.o $(BUILDDIR)/ogg.o $(BUILDDIR)/mpeg.o $(BUILDDIR)/id3.o $(BUILDDIR)/midi.o +OBJ=\ + $(BUILDDIR)/audioextract.o \ + $(BUILDDIR)/wave.o \ + $(BUILDDIR)/ogg.o \ + $(BUILDDIR)/mpeg.o \ + $(BUILDDIR)/id3.o \ + $(BUILDDIR)/midi.o \ + $(BUILDDIR)/mod.o CC=gcc CFLAGS=-Wall -std=gnu99 -O2 -fmessage-length=0 -g BIN=$(BUILDDIR)/audioextract @@ -12,7 +19,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 +$(BUILDDIR)/audioextract.o: audioextract.c audioextract.h ogg.h wave.h mpeg.h id3.h midi.h mod.h $(CC) $(CFLAGS) $< -o $@ -c $(BUILDDIR)/wave.o: wave.c audioextract.h wave.h @@ -30,6 +37,9 @@ $(BUILDDIR)/id3.o: id3.c audioextract.h id3.h $(BUILDDIR)/midi.o: midi.c audioextract.h midi.h $(CC) $(CFLAGS) $< -o $@ -c +$(BUILDDIR)/mod.o: mod.c audioextract.h mod.h + $(CC) $(CFLAGS) $< -o $@ -c + install: all install -s -D $(BIN) "$(PREFIX)/bin/audioextract" diff --git a/README.md b/README.md index a1ca29f..b6f6615 100644 --- a/README.md +++ b/README.md @@ -35,10 +35,11 @@ using the `--min-size` one can hopefully extract only real MPEG files. -f, --formats=FORMATS Comma separated list of formats (file magics) to extract. Supported formats: all all supported formats - default the default set of formats (AIFF, ID3v2, Ogg, RIFF, MIDI) + 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 midi MIDI files + mod MOD files mpeg any MPEG files (e.g. MP3) ogg Ogg files (Vorbis, FLAC, Opus, Theora, etc.) riff little-endian (Windows) wave files @@ -56,7 +57,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: - audioextract --formats=all,-wave data.bin + ./build/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 84c0a24..5809ad3 100644 --- a/audioextract.c +++ b/audioextract.c @@ -17,6 +17,7 @@ #include "mpeg.h" #include "id3.h" #include "midi.h" +#include "mod.h" enum fileformat { NONE = 0, @@ -26,13 +27,16 @@ enum fileformat { MPEG = 8, ID3v2 = 16, MIDI = 32, + MOD = 64, // TODO: -// MOD = 64, // S3M = 128, // IT = 256, // XM = 512, }; +#define ALL_FORMATS (OGG | RIFF | AIFF | MPEG | ID3v2 | MIDI | MOD) +#define DEFAULT_FORMATS (OGG | RIFF | AIFF | ID3v2 | MIDI | MOD) + int usage(int argc, char **argv) { const char *progname = argc <= 0 ? "audioextract" : argv[0]; @@ -48,10 +52,11 @@ int usage(int argc, char **argv) " -f, --formats=FORMATS Comma separated list of formats (file magics) to extract.\n" " Supported formats:\n" " all all supported formats\n" - " default the default set of formats (AIFF, ID3v2, Ogg, RIFF, MIDI)\n" + " 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" " midi MIDI files\n" + " mod MOD 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" @@ -127,6 +132,15 @@ const unsigned char *findmagic(const unsigned char *start, const unsigned char * *format = MPEG; return start; } + else if (formats & MOD && (size_t)(end - start) >= MOD_MAGIC_OFFSET) + { + uint32_t modmagic = MAGIC(start + MOD_MAGIC_OFFSET); + if (IS_MOD_MAGIC(modmagic)) + { + *format = MOD; + return start; + } + } } return NULL; @@ -345,6 +359,15 @@ int extract(const char *filepath, const char *outdir, size_t minsize, size_t max else ++ ptr; break; + case MOD: + if (mod_isfile(ptr, end, &length)) + { + WRITE_FILE(ptr, length, "mod"); + ptr += length; + } + else ++ ptr; + break; + case NONE: ++ ptr; break; @@ -421,13 +444,17 @@ int parse_formats(const char *formats) { mask = MIDI; } + else if (strncasecmp("mod", start, len) == 0) + { + mask = MOD; + } else if (strncasecmp("all", start, len) == 0) { - mask = OGG | RIFF | AIFF | MPEG | ID3v2 | MIDI; + mask = ALL_FORMATS; } else if (strncasecmp("default", start, len) == 0) { - mask = OGG | RIFF | AIFF | ID3v2 | MIDI; + mask = DEFAULT_FORMATS; } else if (len != 0) { @@ -468,7 +495,7 @@ int main(int argc, char **argv) size_t numfiles = 0; size_t minsize = 0; size_t maxsize = (size_t)-1; - int formats = OGG | RIFF | AIFF | ID3v2 | MIDI; + int formats = DEFAULT_FORMATS; const char *outdir = "."; long long tmp = 0; size_t size = 0; diff --git a/mod.c b/mod.c new file mode 100644 index 0000000..41068a1 --- /dev/null +++ b/mod.c @@ -0,0 +1,49 @@ +#include "mod.h" + +int mod_isfile(const unsigned char *start, const unsigned char *end, size_t *lengthptr) +{ + size_t input_len = (size_t)(end - start); + size_t length = MOD_MAGIC_OFFSET + 4; + size_t channels = 0; + size_t patterns = 0; + + if (input_len < length) + return 0; + + for (const unsigned char *ptr = start + 20, *sample_end = ptr + 31*30; + ptr < sample_end; ptr += 30) + { + length += (size_t)be16toh(*(const uint16_t*)(ptr + 22)) << 1; + } + + uint8_t song_length = start[950]; + if (song_length > 0x80) + return 0; + + for (const unsigned char *ptr = start + 952, *pattern_table_end = ptr + 128; + ptr < pattern_table_end; ++ ptr) + { + if (*ptr > patterns) patterns = *ptr; + } + ++ patterns; + + uint32_t magic = MAGIC(start + MOD_MAGIC_OFFSET); + + if (IS_MOD_4CH_MAGIC(magic)) channels = 4; + else if (IS_MOD_5CH_MAGIC(magic)) channels = 5; + else if (IS_MOD_6CH_MAGIC(magic)) channels = 6; + else if (IS_MOD_7CH_MAGIC(magic)) channels = 7; + else if (IS_MOD_8CH_MAGIC(magic)) channels = 8; + else if (IS_MOD_16CH_MAGIC(magic)) channels = 16; + else if (IS_MOD_32CH_MAGIC(magic)) channels = 32; + else + return 0; + + length += patterns * channels * 64 * 4; + if (input_len < length) + return 0; + + if (lengthptr) *lengthptr = length; + + return 1; +} diff --git a/mod.h b/mod.h new file mode 100644 index 0000000..2ca1793 --- /dev/null +++ b/mod.h @@ -0,0 +1,80 @@ +#ifndef AUDIOEXTRACT_MOD_H__ +#define AUDIOEXTRACT_MOD_H__ + +#include "audioextract.h" + +#define MOD_4CH_MAGIC1 MAGIC("M.K.") +#define MOD_4CH_MAGIC2 MAGIC("M!K!") +#define MOD_4CH_MAGIC3 MAGIC("M&K!") +#define MOD_4CH_MAGIC4 MAGIC("N.T.") +#define MOD_4CH_MAGIC5 MAGIC("FLT4") +#define MOD_4CH_MAGIC6 MAGIC("4CHN") +#define MOD_4CH_MAGIC7 MAGIC("TDZ4") + +#define MOD_5CH_MAGIC1 MAGIC("TDZ5") /* not sure if this exists */ + +#define MOD_6CH_MAGIC1 MAGIC("6CHN") +#define MOD_6CH_MAGIC2 MAGIC("TDZ6") + +#define MOD_7CH_MAGIC1 MAGIC("TDZ7") /* not sure if this exists */ + +#define MOD_8CH_MAGIC1 MAGIC("8CHN") +#define MOD_8CH_MAGIC2 MAGIC("TDZ8") +#define MOD_8CH_MAGIC3 MAGIC("FLT8") +#define MOD_8CH_MAGIC4 MAGIC("CD81") +#define MOD_8CH_MAGIC5 MAGIC("OKTA") + +#define MOD_16CH_MAGIC1 MAGIC("16CH") +#define MOD_16CH_MAGIC2 MAGIC("16CN") + +#define MOD_32CH_MAGIC1 MAGIC("32CH") +#define MOD_32CH_MAGIC2 MAGIC("32CN") + +#define IS_MOD_MAGIC(magic) \ + (IS_MOD_4CH_MAGIC(magic) || \ + IS_MOD_5CH_MAGIC(magic) || \ + IS_MOD_6CH_MAGIC(magic) || \ + IS_MOD_7CH_MAGIC(magic) || \ + IS_MOD_8CH_MAGIC(magic) || \ + IS_MOD_16CH_MAGIC(magic) || \ + IS_MOD_32CH_MAGIC(magic)) + +#define IS_MOD_4CH_MAGIC(magic) \ + (((magic) == MOD_4CH_MAGIC1) || \ + ((magic) == MOD_4CH_MAGIC2) || \ + ((magic) == MOD_4CH_MAGIC3) || \ + ((magic) == MOD_4CH_MAGIC4) || \ + ((magic) == MOD_4CH_MAGIC5) || \ + ((magic) == MOD_4CH_MAGIC6) || \ + ((magic) == MOD_4CH_MAGIC7)) + +#define IS_MOD_5CH_MAGIC(magic) \ + ((magic) == MOD_5CH_MAGIC1) + +#define IS_MOD_6CH_MAGIC(magic) \ + (((magic) == MOD_6CH_MAGIC1) || \ + ((magic) == MOD_6CH_MAGIC2)) + +#define IS_MOD_7CH_MAGIC(magic) \ + ((magic) == MOD_7CH_MAGIC1) + +#define IS_MOD_8CH_MAGIC(magic) \ + (((magic) == MOD_8CH_MAGIC1) || \ + ((magic) == MOD_8CH_MAGIC2) || \ + ((magic) == MOD_8CH_MAGIC3) || \ + ((magic) == MOD_8CH_MAGIC4) || \ + ((magic) == MOD_8CH_MAGIC5)) + +#define IS_MOD_16CH_MAGIC(magic) \ + (((magic) == MOD_16CH_MAGIC1) || \ + ((magic) == MOD_16CH_MAGIC2)) + +#define IS_MOD_32CH_MAGIC(magic) \ + (((magic) == MOD_32CH_MAGIC1) || \ + ((magic) == MOD_32CH_MAGIC2)) + +#define MOD_MAGIC_OFFSET 1080 + +int mod_isfile(const unsigned char *start, const unsigned char *end, size_t *lengthptr); + +#endif /* AUDIOEXTRACT_MOD_H__ */