diff --git a/Linux/CodeBlocks/QuakeSpasm-SDL2.cbp b/Linux/CodeBlocks/QuakeSpasm-SDL2.cbp
index 09479794..9f93b014 100644
--- a/Linux/CodeBlocks/QuakeSpasm-SDL2.cbp
+++ b/Linux/CodeBlocks/QuakeSpasm-SDL2.cbp
@@ -266,6 +266,9 @@
+
+
+
diff --git a/Linux/CodeBlocks/QuakeSpasm.cbp b/Linux/CodeBlocks/QuakeSpasm.cbp
index 3432f9e2..a93f9abd 100644
--- a/Linux/CodeBlocks/QuakeSpasm.cbp
+++ b/Linux/CodeBlocks/QuakeSpasm.cbp
@@ -265,6 +265,9 @@
+
+
+
diff --git a/MacOSX/QuakeSpasm.xcodeproj/project.pbxproj b/MacOSX/QuakeSpasm.xcodeproj/project.pbxproj
index 37d57ae3..be73842c 100644
--- a/MacOSX/QuakeSpasm.xcodeproj/project.pbxproj
+++ b/MacOSX/QuakeSpasm.xcodeproj/project.pbxproj
@@ -105,6 +105,8 @@
48E2EC8815FB516600B8D476 /* libvorbis.dylib in Copy Libraries */ = {isa = PBXBuildFile; fileRef = 48E2EC7B15FB507A00B8D476 /* libvorbis.dylib */; };
48E2EC8915FB516600B8D476 /* libvorbisfile.dylib in Copy Libraries */ = {isa = PBXBuildFile; fileRef = 48E2EC7C15FB507A00B8D476 /* libvorbisfile.dylib */; };
48FE585B0D3A82C8006BB491 /* QuakeArguments.m in Sources */ = {isa = PBXBuildFile; fileRef = 48FE585A0D3A82C8006BB491 /* QuakeArguments.m */; };
+ 63D6EB3523A255900047028C /* snd_mp3tag.c in Sources */ = {isa = PBXBuildFile; fileRef = 63D6EB3423A255900047028C /* snd_mp3tag.c */; };
+ 63D6EB3623A255900047028C /* snd_mp3tag.c in Sources */ = {isa = PBXBuildFile; fileRef = 63D6EB3423A255900047028C /* snd_mp3tag.c */; };
664D988A19CF6B78000D395C /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; };
664D988B19CF6B78000D395C /* Launcher.nib in Resources */ = {isa = PBXBuildFile; fileRef = 48B9E7860D340B1E0001CACF /* Launcher.nib */; };
664D988C19CF6B78000D395C /* QuakeSpasm.icns in Resources */ = {isa = PBXBuildFile; fileRef = 484AA4B30D3FF6C0005D917A /* QuakeSpasm.icns */; };
@@ -470,6 +472,7 @@
48E2EC7C15FB507A00B8D476 /* libvorbisfile.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libvorbisfile.dylib; path = codecs/lib/libvorbisfile.dylib; sourceTree = ""; };
48FE58590D3A82C8006BB491 /* QuakeArguments.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QuakeArguments.h; sourceTree = ""; };
48FE585A0D3A82C8006BB491 /* QuakeArguments.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QuakeArguments.m; sourceTree = ""; };
+ 63D6EB3423A255900047028C /* snd_mp3tag.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = snd_mp3tag.c; path = ../Quake/snd_mp3tag.c; sourceTree = SOURCE_ROOT; };
664D98F919CF6B78000D395C /* QuakeSpasm-SDL2.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "QuakeSpasm-SDL2.app"; sourceTree = BUILT_PRODUCTS_DIR; };
664D98FB19CF6B78000D395C /* Info copy.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info copy.plist"; sourceTree = ""; };
664D990519CF6E16000D395C /* SDL2.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = SDL2.framework; sourceTree = SOURCE_ROOT; };
@@ -716,6 +719,7 @@
486577CA0D31A22A00E7920A /* snd_mix.c */,
483A78540D2EEAC300CB2E4C /* snd_sdl.c */,
4854B1B01340C646004C9F45 /* snd_mp3.c */,
+ 63D6EB3423A255900047028C /* snd_mp3tag.c */,
4885A84A179740A0000EC703 /* snd_opus.c */,
483A78640D2EEAF000CB2E4C /* snd_mikmod.c */,
4818B0B012D5BA1A006DD66E /* snd_umx.c */,
@@ -1029,6 +1033,7 @@
664D98DA19CF6B78000D395C /* strlcpy.c in Sources */,
664D98DB19CF6B78000D395C /* snd_opus.c in Sources */,
664D98DC19CF6B78000D395C /* snd_flac.c in Sources */,
+ 63D6EB3623A255900047028C /* snd_mp3tag.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1115,6 +1120,7 @@
48A7C1FD14AA34940011B754 /* strlcpy.c in Sources */,
4885A84C179740A0000EC703 /* snd_opus.c in Sources */,
48281301179C3F13004E1D61 /* snd_flac.c in Sources */,
+ 63D6EB3523A255900047028C /* snd_mp3tag.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/MacOSX/QuakeSpasmPPC.xcodeproj/project.pbxproj b/MacOSX/QuakeSpasmPPC.xcodeproj/project.pbxproj
index cae6296d..55671d87 100644
--- a/MacOSX/QuakeSpasmPPC.xcodeproj/project.pbxproj
+++ b/MacOSX/QuakeSpasmPPC.xcodeproj/project.pbxproj
@@ -105,6 +105,7 @@
48E2EC8815FB516600B8D476 /* libvorbis.dylib in Copy Libraries */ = {isa = PBXBuildFile; fileRef = 48E2EC7B15FB507A00B8D476 /* libvorbis.dylib */; };
48E2EC8915FB516600B8D476 /* libvorbisfile.dylib in Copy Libraries */ = {isa = PBXBuildFile; fileRef = 48E2EC7C15FB507A00B8D476 /* libvorbisfile.dylib */; };
48FE585B0D3A82C8006BB491 /* QuakeArguments.m in Sources */ = {isa = PBXBuildFile; fileRef = 48FE585A0D3A82C8006BB491 /* QuakeArguments.m */; };
+ 63D6EB5F23A2563B0047028C /* snd_mp3tag.c in Sources */ = {isa = PBXBuildFile; fileRef = 63D6EB5E23A2563B0047028C /* snd_mp3tag.c */; };
8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; };
8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; };
A4E3AF2A05D43FCC000F1B47 /* libmikmod.dylib in Copy Libraries */ = {isa = PBXBuildFile; fileRef = F5F5DE80017CB4370103A810 /* libmikmod.dylib */; };
@@ -301,6 +302,7 @@
48FE58590D3A82C8006BB491 /* QuakeArguments.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QuakeArguments.h; sourceTree = ""; };
48FE585A0D3A82C8006BB491 /* QuakeArguments.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QuakeArguments.m; sourceTree = ""; };
664D98FB19CF6B78000D395C /* Info copy.plist */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Info copy.plist"; sourceTree = ""; };
+ 63D6EB5E23A2563B0047028C /* snd_mp3tag.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = snd_mp3tag.c; path = ../Quake/snd_mp3tag.c; sourceTree = SOURCE_ROOT; };
664D990519CF6E16000D395C /* SDL2.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = SDL2.framework; sourceTree = SOURCE_ROOT; };
8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; };
8D1107320486CEB800E47090 /* QuakeSpasm.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = QuakeSpasm.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -519,6 +521,7 @@
486577CA0D31A22A00E7920A /* snd_mix.c */,
483A78540D2EEAC300CB2E4C /* snd_sdl.c */,
4854B1B01340C646004C9F45 /* snd_mp3.c */,
+ 63D6EB5E23A2563B0047028C /* snd_mp3tag.c */,
4885A84A179740A0000EC703 /* snd_opus.c */,
483A78640D2EEAF000CB2E4C /* snd_mikmod.c */,
4818B0B012D5BA1A006DD66E /* snd_umx.c */,
@@ -786,6 +789,7 @@
48A7C1FD14AA34940011B754 /* strlcpy.c in Sources */,
4885A84C179740A0000EC703 /* snd_opus.c in Sources */,
48281301179C3F13004E1D61 /* snd_flac.c in Sources */,
+ 63D6EB5F23A2563B0047028C /* snd_mp3tag.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/Quake/Makefile b/Quake/Makefile
index 2b09be95..fe1ae833 100644
--- a/Quake/Makefile
+++ b/Quake/Makefile
@@ -124,11 +124,11 @@ $(error Invalid MP3LIB setting)
endif
endif
ifeq ($(MP3LIB),mad)
-mp3_obj=snd_mp3.o
+mp3_obj=snd_mp3
lib_mp3dec=-lmad
endif
ifeq ($(MP3LIB),mpg123)
-mp3_obj=snd_mpg123.o
+mp3_obj=snd_mpg123
lib_mp3dec=-lmpg123
endif
ifeq ($(VORBISLIB),vorbis)
@@ -205,7 +205,8 @@ MUSIC_OBJS:= bgmusic.o \
snd_wave.o \
snd_vorbis.o \
snd_opus.o \
- $(mp3_obj) \
+ $(mp3_obj).o \
+ snd_mp3tag.o \
snd_mikmod.o \
snd_xmp.o \
snd_umx.o
diff --git a/Quake/Makefile.darwin b/Quake/Makefile.darwin
index b2afdbb1..aaad1967 100644
--- a/Quake/Makefile.darwin
+++ b/Quake/Makefile.darwin
@@ -137,11 +137,11 @@ $(error Invalid MP3LIB setting)
endif
endif
ifeq ($(MP3LIB),mad)
-mp3_obj=snd_mp3.o
+mp3_obj=snd_mp3
lib_mp3dec=-lmad
endif
ifeq ($(MP3LIB),mpg123)
-mp3_obj=snd_mpg123.o
+mp3_obj=snd_mpg123
lib_mp3dec=-lmpg123
endif
ifeq ($(VORBISLIB),vorbis)
@@ -231,7 +231,8 @@ MUSIC_OBJS:= bgmusic.o \
snd_wave.o \
snd_vorbis.o \
snd_opus.o \
- $(mp3_obj) \
+ $(mp3_obj).o \
+ snd_mp3tag.o \
snd_mikmod.o \
snd_xmp.o \
snd_umx.o
diff --git a/Quake/Makefile.w32 b/Quake/Makefile.w32
index 7ec7b892..71cea828 100644
--- a/Quake/Makefile.w32
+++ b/Quake/Makefile.w32
@@ -103,11 +103,11 @@ $(error Invalid MP3LIB setting)
endif
endif
ifeq ($(MP3LIB),mad)
-mp3_obj=snd_mp3.o
+mp3_obj=snd_mp3
lib_mp3dec=-lmad
endif
ifeq ($(MP3LIB),mpg123)
-mp3_obj=snd_mpg123.o
+mp3_obj=snd_mpg123
lib_mp3dec=-lmpg123
endif
ifeq ($(VORBISLIB),vorbis)
@@ -195,7 +195,8 @@ MUSIC_OBJS:= bgmusic.o \
snd_wave.o \
snd_vorbis.o \
snd_opus.o \
- $(mp3_obj) \
+ $(mp3_obj).o \
+ snd_mp3tag.o \
snd_mikmod.o \
snd_xmp.o \
snd_umx.o
diff --git a/Quake/Makefile.w64 b/Quake/Makefile.w64
index 6b56878e..ab640fdc 100644
--- a/Quake/Makefile.w64
+++ b/Quake/Makefile.w64
@@ -101,11 +101,11 @@ $(error Invalid MP3LIB setting)
endif
endif
ifeq ($(MP3LIB),mad)
-mp3_obj=snd_mp3.o
+mp3_obj=snd_mp3
lib_mp3dec=-lmad
endif
ifeq ($(MP3LIB),mpg123)
-mp3_obj=snd_mpg123.o
+mp3_obj=snd_mpg123
lib_mp3dec=-lmpg123
endif
ifeq ($(VORBISLIB),vorbis)
@@ -193,7 +193,8 @@ MUSIC_OBJS:= bgmusic.o \
snd_wave.o \
snd_vorbis.o \
snd_opus.o \
- $(mp3_obj) \
+ $(mp3_obj).o \
+ snd_mp3tag.o \
snd_mikmod.o \
snd_xmp.o \
snd_umx.o
diff --git a/Quake/OWMakefile.win32 b/Quake/OWMakefile.win32
index 29734d30..6bd01ad7 100644
--- a/Quake/OWMakefile.win32
+++ b/Quake/OWMakefile.win32
@@ -53,11 +53,11 @@ NET_LIBS = $(LIBWINSOCK)
CODEC_INC = -I../Windows/codecs/include
LIBCODEC = ../Windows/codecs/x86-watcom/
!ifeq MP3LIB mad
-mp3_obj=snd_mp3.obj
+mp3_obj=snd_mp3
lib_mp3dec=$(LIBCODEC)mad.lib
!endif
!ifeq MP3LIB mpg123
-mp3_obj=snd_mpg123.obj
+mp3_obj=snd_mpg123
lib_mp3dec=$(LIBCODEC)mpg123.lib
!endif
!ifeq VORBISLIB vorbis
@@ -140,7 +140,8 @@ MUSIC_OBJS= bgmusic.obj &
snd_wave.obj &
snd_vorbis.obj &
snd_opus.obj &
- $(mp3_obj) &
+ $(mp3_obj).obj &
+ snd_mp3tag.obj &
snd_mikmod.obj &
snd_xmp.obj &
snd_umx.obj
diff --git a/Quake/snd_mp3.c b/Quake/snd_mp3.c
index 5014ca57..b091c10a 100644
--- a/Quake/snd_mp3.c
+++ b/Quake/snd_mp3.c
@@ -61,198 +61,6 @@ typedef struct _mp3_priv_t
} mp3_priv_t;
-/* TAG HANDLING:
- * put together by O.Sezer using public specs,
- * put into public domain.
- */
-static inline qboolean tag_is_id3v1(const unsigned char *data, size_t length) {
- /* http://id3.org/ID3v1 : 3 bytes "TAG" identifier and 125 bytes tag data */
- if (length < 3 || memcmp(data,"TAG",3) != 0) {
- return false;
- }
- return true;
-}
-static inline qboolean tag_is_id3v2(const unsigned char *data, size_t length) {
- /* ID3v2 header is 10 bytes: http://id3.org/id3v2.4.0-structure */
- /* bytes 0-2: "ID3" identifier */
- if (length < 10 || memcmp(data,"ID3",3) != 0) {
- return false;
- }
- /* bytes 3-4: version num (major,revision), each byte always less than 0xff. */
- if (data[3] == 0xff || data[4] == 0xff) {
- return false;
- }
- /* bytes 6-9 are the ID3v2 tag size: a 32 bit 'synchsafe' integer, i.e. the
- * highest bit 7 in each byte zeroed. i.e.: 7 bit information in each byte ->
- * effectively a 28 bit value. */
- if (data[6] >= 0x80 || data[7] >= 0x80 || data[8] >= 0x80 || data[9] >= 0x80) {
- return false;
- }
- return true;
-}
-static inline long get_id3v2_len(const unsigned char *data, long length) {
- /* size is a 'synchsafe' integer (see above) */
- long size = (long)((data[6]<<21) + (data[7]<<14) + (data[8]<<7) + data[9]);
- size += 10; /* header size */
- /* ID3v2 header[5] is flags (bits 4-7 only, 0-3 are zero).
- * bit 4 set: footer is present (a copy of the header but
- * with "3DI" as ident.) */
- if (data[5] & 0x10) {
- size += 10; /* footer size */
- }
- /* optional padding (always zeroes) */
- while (size < length && data[size] == 0) {
- ++size;
- }
- return size;
-}
-static inline qboolean tag_is_apetag(const unsigned char *data, size_t length) {
- /* http://wiki.hydrogenaud.io/index.php?title=APEv2_specification
- * Header/footer is 32 bytes: bytes 0-7 ident, bytes 8-11 version,
- * bytes 12-17 size. bytes 24-31 are reserved: must be all zeroes. */
- unsigned int v;
-
- if (length < 32 || memcmp(data,"APETAGEX",8) != 0) {
- return false;
- }
- v = (unsigned)((data[11]<<24) | (data[10]<<16) | (data[9]<<8) | data[8]); /* version */
- if (v != 2000U && v != 1000U) {
- return false;
- }
- v = 0; /* reserved bits : */
- if (memcmp(&data[24],&v,4) != 0 || memcmp(&data[28],&v,4) != 0) {
- return false;
- }
- return true;
-}
-static inline long get_ape_len(const unsigned char *data) {
- unsigned int flags, version;
- long size = (long)((data[15]<<24) | (data[14]<<16) | (data[13]<<8) | data[12]);
- version = (unsigned)((data[11]<<24) | (data[10]<<16) | (data[9]<<8) | data[8]);
- flags = (unsigned)((data[23]<<24) | (data[22]<<16) | (data[21]<<8) | data[20]);
- if (version == 2000U && (flags & (1U<<31))) size += 32; /* header present. */
- return size;
-}
-static inline int is_lyrics3tag(const unsigned char *data, size_t length) {
- /* http://id3.org/Lyrics3
- * http://id3.org/Lyrics3v2 */
- if (length < 15) return 0;
- if (memcmp(data+6,"LYRICS200",9) == 0) return 2; /* v2 */
- if (memcmp(data+6,"LYRICSEND",9) == 0) return 1; /* v1 */
- return 0;
-}
-static inline long get_lyrics3v1_len(snd_stream_t *stream, unsigned char *buf) {
- const char *p; long i, len;
- /* needs manual search: http://id3.org/Lyrics3 */
- /* this relies on the input_buffer size >= 5100 */
- if (stream->fh.length < 20) return -1;
- len = (stream->fh.length > 5109)? 5109 : stream->fh.length;
- FS_fseek(&stream->fh, -len, SEEK_END);
- FS_fread(buf, 1, (len -= 9), &stream->fh); /* exclude footer */
- /* strstr() won't work here. */
- for (i = len - 11, p = (const char*)buf; i >= 0; --i, ++p) {
- if (memcmp(p, "LYRICSBEGIN", 11) == 0)
- break;
- }
- if (i < 0) return -1;
- return len - (long)(p - (const char*)buf) + 9 /* footer */;
-}
-static inline long get_lyrics3v2_len(const unsigned char *data, size_t length) {
- /* 6 bytes before the end marker is size in decimal format -
- * does not include the 9 bytes end marker and size field. */
- if (length != 6) return 0;
- return strtol((const char *)data, NULL, 10) + 15;
-}
-static inline qboolean verify_lyrics3v2(const unsigned char *data, size_t length) {
- if (length < 11) return false;
- if (memcmp(data,"LYRICSBEGIN",11) == 0) return true;
- return false;
-}
-
-static int mp3_skiptags(snd_stream_t *stream, unsigned char *buf, size_t bufsize)
-{
- long len; size_t readsize;
- int rc = -1;
-
- readsize = FS_fread(buf, 1, bufsize, &stream->fh);
- if (!readsize || FS_ferror(&stream->fh)) goto fail;
-
- /* ID3v2 tag is at the start */
- if (tag_is_id3v2(buf, readsize)) {
- len = get_id3v2_len(buf, (long)readsize);
- if (len >= stream->fh.length) goto fail;
- stream->fh.start += len;
- stream->fh.length -= len;
- Con_DPrintf("MP3: skipped %ld bytes ID3v2 tag\n", len);
- }
- /* APE tag _might_ be at the start (discouraged
- * but not forbidden, either.) read the header. */
- else if (tag_is_apetag(buf, readsize)) {
- len = get_ape_len(buf);
- if (len >= stream->fh.length) goto fail;
- stream->fh.start += len;
- stream->fh.length -= len;
- Con_DPrintf("MP3: skipped %ld bytes APE tag\n", len);
- }
-
- /* ID3v1 tag is at the end */
- if (stream->fh.length >= 128) {
- FS_fseek(&stream->fh, -128, SEEK_END);
- readsize = FS_fread(buf, 1, 128, &stream->fh);
- if (readsize != 128) goto fail;
- if (tag_is_id3v1(buf, 128)) {
- stream->fh.length -= 128;
- Con_DPrintf("MP3: skipped %ld bytes ID3v1 tag\n", 128L);
- /* FIXME: handle possible double-ID3v1 tags? */
- }
- }
-
- /* do we know whether ape or lyrics3 is the first?
- * well, we don't: we need to handle that later... */
-
- /* APE tag may be at the end: read the footer */
- if (stream->fh.length >= 32) {
- FS_fseek(&stream->fh, -32, SEEK_END);
- readsize = FS_fread(buf, 1, 32, &stream->fh);
- if (readsize != 32) goto fail;
- if (tag_is_apetag(buf, 32)) {
- len = get_ape_len(buf);
- if (len >= stream->fh.length) goto fail;
- stream->fh.length -= len;
- Con_DPrintf("MP3: skipped %ld bytes APE tag\n", len);
- }
- }
-
- if (stream->fh.length >= 15) {
- FS_fseek(&stream->fh, -15, SEEK_END);
- readsize = FS_fread(buf, 1, 15, &stream->fh);
- if (readsize != 15) goto fail;
- len = is_lyrics3tag(buf, 15);
- if (len == 2) {
- len = get_lyrics3v2_len(buf, 6);
- if (len >= stream->fh.length) goto fail;
- if (len < 15) goto fail;
- FS_fseek(&stream->fh, -len, SEEK_END);
- readsize = FS_fread(buf, 1, 11, &stream->fh);
- if (readsize != 11) goto fail;
- if (!verify_lyrics3v2(buf, 11)) goto fail;
- stream->fh.length -= len;
- Con_DPrintf("MP3: skipped %ld bytes Lyrics3 tag\n", len);
- }
- else if (len == 1) {
- len = get_lyrics3v1_len(stream, buf);
- if (len < 0) goto fail;
- stream->fh.length -= len;
- Con_DPrintf("MP3: skipped %ld bytes Lyrics3 tag\n", len);
- }
- }
-
- rc = 0;
- fail:
- FS_rewind(&stream->fh);
- return (stream->fh.length > 0)? rc : -1;
-}
-
/* (Re)fill the stream buffer that is to be decoded. If any data
* still exists in the buffer then they are first shifted to be
* front of the stream buffer. */
@@ -292,7 +100,7 @@ static int mp3_startread(snd_stream_t *stream)
mp3_priv_t *p = (mp3_priv_t *) stream->priv;
size_t ReadSize;
- if (mp3_skiptags(stream, p->mp3_buffer, MP3_BUFFER_SIZE) < 0)
+ if (mp3_skiptags(stream) < 0)
return -1;
mad_stream_init(&p->Stream);
diff --git a/Quake/snd_mp3.h b/Quake/snd_mp3.h
index d54ae4dc..b519a9d1 100644
--- a/Quake/snd_mp3.h
+++ b/Quake/snd_mp3.h
@@ -6,6 +6,7 @@
#if defined(USE_CODEC_MP3)
extern snd_codec_t mp3_codec;
+int mp3_skiptags(snd_stream_t *);
#endif /* USE_CODEC_MP3 */
diff --git a/Quake/snd_mp3tag.c b/Quake/snd_mp3tag.c
new file mode 100644
index 00000000..382f810a
--- /dev/null
+++ b/Quake/snd_mp3tag.c
@@ -0,0 +1,339 @@
+/* MP3 TAGS STUFF -- put together using public specs.
+ * Copyright (C) 2018-2019 O.Sezer
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "quakedef.h"
+
+#if defined(USE_CODEC_MP3)
+#include "snd_codec.h"
+#include "q_ctype.h"
+
+static inline qboolean tag_is_id3v1(const unsigned char *data, size_t length) {
+ /* http://id3.org/ID3v1 : 3 bytes "TAG" identifier and 125 bytes tag data */
+ if (length < 3 || memcmp(data,"TAG",3) != 0) {
+ return false;
+ }
+ return true;
+}
+static inline qboolean tag_is_id3v2(const unsigned char *data, size_t length) {
+ /* ID3v2 header is 10 bytes: http://id3.org/id3v2.4.0-structure */
+ /* bytes 0-2: "ID3" identifier */
+ if (length < 10 || memcmp(data,"ID3",3) != 0) {
+ return false;
+ }
+ /* bytes 3-4: version num (major,revision), each byte always less than 0xff. */
+ if (data[3] == 0xff || data[4] == 0xff) {
+ return false;
+ }
+ /* bytes 6-9 are the ID3v2 tag size: a 32 bit 'synchsafe' integer, i.e. the
+ * highest bit 7 in each byte zeroed. i.e.: 7 bit information in each byte ->
+ * effectively a 28 bit value. */
+ if (data[6] >= 0x80 || data[7] >= 0x80 || data[8] >= 0x80 || data[9] >= 0x80) {
+ return false;
+ }
+ return true;
+}
+static inline long get_id3v2_len(const unsigned char *data, long length) {
+ /* size is a 'synchsafe' integer (see above) */
+ long size = (long)((data[6]<<21) + (data[7]<<14) + (data[8]<<7) + data[9]);
+ size += 10; /* header size */
+ /* ID3v2 header[5] is flags (bits 4-7 only, 0-3 are zero).
+ * bit 4 set: footer is present (a copy of the header but
+ * with "3DI" as ident.) */
+ if (data[5] & 0x10) {
+ size += 10; /* footer size */
+ }
+ /* optional padding (always zeroes) */
+ while (size < length && data[size] == 0) {
+ ++size;
+ }
+ return size;
+}
+static inline qboolean tag_is_apetag(const unsigned char *data, size_t length) {
+ /* http://wiki.hydrogenaud.io/index.php?title=APEv2_specification
+ * Header/footer is 32 bytes: bytes 0-7 ident, bytes 8-11 version,
+ * bytes 12-17 size. bytes 24-31 are reserved: must be all zeroes. */
+ unsigned int v;
+
+ if (length < 32 || memcmp(data,"APETAGEX",8) != 0) {
+ return false;
+ }
+ v = (unsigned)((data[11]<<24) | (data[10]<<16) | (data[9]<<8) | data[8]); /* version */
+ if (v != 2000U && v != 1000U) {
+ return false;
+ }
+ v = 0; /* reserved bits : */
+ if (memcmp(&data[24],&v,4) != 0 || memcmp(&data[28],&v,4) != 0) {
+ return false;
+ }
+ return true;
+}
+static inline long get_ape_len(const unsigned char *data) {
+ unsigned int flags, version;
+ long size = (long)((data[15]<<24) | (data[14]<<16) | (data[13]<<8) | data[12]);
+ version = (unsigned)((data[11]<<24) | (data[10]<<16) | (data[9]<<8) | data[8]);
+ flags = (unsigned)((data[23]<<24) | (data[22]<<16) | (data[21]<<8) | data[20]);
+ if (version == 2000U && (flags & (1U<<31))) size += 32; /* header present. */
+ return size;
+}
+static inline int is_lyrics3tag(const unsigned char *data, size_t length) {
+ /* http://id3.org/Lyrics3
+ * http://id3.org/Lyrics3v2 */
+ if (length < 15) return 0;
+ if (memcmp(data+6,"LYRICS200",9) == 0) return 2; /* v2 */
+ if (memcmp(data+6,"LYRICSEND",9) == 0) return 1; /* v1 */
+ return 0;
+}
+static inline long get_lyrics3v1_len(snd_stream_t *stream) {
+ const char *p; long i, len;
+ char buf[5104];
+ /* needs manual search: http://id3.org/Lyrics3 */
+ if (stream->fh.length < 20) return -1;
+ len = (stream->fh.length > 5109)? 5109 : stream->fh.length;
+ FS_fseek(&stream->fh, -len, SEEK_END);
+ FS_fread(buf, 1, (len -= 9), &stream->fh); /* exclude footer */
+ /* strstr() won't work here. */
+ for (i = len - 11, p = buf; i >= 0; --i, ++p) {
+ if (memcmp(p, "LYRICSBEGIN", 11) == 0)
+ break;
+ }
+ if (i < 0) return -1;
+ return len - (long)(p - buf) + 9 /* footer */;
+}
+static inline long get_lyrics3v2_len(const unsigned char *data, size_t length) {
+ /* 6 bytes before the end marker is size in decimal format -
+ * does not include the 9 bytes end marker and size field. */
+ if (length != 6) return 0;
+ return strtol((const char *)data, NULL, 10) + 15;
+}
+static inline qboolean verify_lyrics3v2(const unsigned char *data, size_t length) {
+ if (length < 11) return false;
+ if (memcmp(data,"LYRICSBEGIN",11) == 0) return true;
+ return false;
+}
+#define MMTAG_PARANOID
+static inline qboolean is_musicmatch(const unsigned char *data, long length) {
+ /* From docs/musicmatch.txt in id3lib: https://sourceforge.net/projects/id3lib/
+ Overall tag structure:
+
+ +-----------------------------+
+ | Header |
+ | (256 bytes, OPTIONAL) |
+ +-----------------------------+
+ | Image extension (4 bytes) |
+ +-----------------------------+
+ | Image binary |
+ | (var. length >= 4 bytes) |
+ +-----------------------------+
+ | Unused (4 bytes) |
+ +-----------------------------+
+ | Version info (256 bytes) |
+ +-----------------------------+
+ | Audio meta-data |
+ | (var. length >= 7868 bytes) |
+ +-----------------------------+
+ | Data offsets (20 bytes) |
+ +-----------------------------+
+ | Footer (48 bytes) |
+ +-----------------------------+
+ */
+ if (length < 48) return false;
+ /* sig: 19 bytes company name + 13 bytes space */
+ if (memcmp(data,"Brava Software Inc. ",32) != 0) {
+ return false;
+ }
+ /* 4 bytes version: x.xx */
+ if (!q_isdigit(data[32]) || data[33] != '.' ||
+ !q_isdigit(data[34]) ||!q_isdigit(data[35])) {
+ return false;
+ }
+ #ifdef MMTAG_PARANOID
+ /* [36..47]: 12 bytes trailing space */
+ for (length = 36; length < 48; ++length) {
+ if (data[length] != ' ') return false;
+ }
+ #endif
+ return true;
+}
+static inline long get_musicmatch_len(snd_stream_t *stream) {
+ const int metasizes[4] = { 7868, 7936, 8004, 8132 };
+ const unsigned char syncstr[10] = {'1','8','2','7','3','6','4','5',0,0};
+ unsigned char buf[256];
+ int i, j, imgext_ofs, version_ofs;
+ long len;
+
+ FS_fseek(&stream->fh, -68, SEEK_END);
+ FS_fread(buf, 1, 20, &stream->fh);
+ imgext_ofs = (int)((buf[3] <<24) | (buf[2] <<16) | (buf[1] <<8) | buf[0] );
+ version_ofs = (int)((buf[15]<<24) | (buf[14]<<16) | (buf[13]<<8) | buf[12]);
+ if (version_ofs <= imgext_ofs) return -1;
+ if (version_ofs <= 0 || imgext_ofs <= 0) return -1;
+ /* Try finding the version info section:
+ * Because metadata section comes after it, and because metadata section
+ * has different sizes across versions (format ver. <= 3.00: always 7868
+ * bytes), we can _not_ directly calculate using deltas from the offsets
+ * section. */
+ for (i = 0; i < 4; ++i) {
+ /* 48: footer, 20: offsets, 256: version info */
+ len = metasizes[i] + 48 + 20 + 256;
+ if (stream->fh.length < len) return -1;
+ FS_fseek(&stream->fh, -len, SEEK_END);
+ FS_fread(buf, 1, 256, &stream->fh);
+ /* [0..9]: sync string, [30..255]: 0x20 */
+ #ifdef MMTAG_PARANOID
+ for (j = 30; j < 256; ++j) {
+ if (buf[j] != ' ') break;
+ }
+ if (j < 256) continue;
+ #endif
+ if (memcmp(buf, syncstr, 10) == 0) {
+ break;
+ }
+ }
+ if (i == 4) return -1; /* no luck. */
+ #ifdef MMTAG_PARANOID
+ /* unused section: (4 bytes of 0x00) */
+ FS_fseek(&stream->fh, -(len + 4), SEEK_END);
+ FS_fread(buf, 1, 4, &stream->fh); j = 0;
+ if (memcmp(buf, &j, 4) != 0) return -1;
+ #endif
+ len += (version_ofs - imgext_ofs);
+ if (stream->fh.length < len) return -1;
+ FS_fseek(&stream->fh, -len, SEEK_END);
+ FS_fread(buf, 1, 8, &stream->fh);
+ j = (int)((buf[7] <<24) | (buf[6] <<16) | (buf[5] <<8) | buf[4]);
+ if (j < 0) return -1;
+ /* verify image size: */
+ /* without this, we may land at a wrong place. */
+ if (j + 12 != version_ofs - imgext_ofs) return -1;
+ /* try finding the optional header */
+ if (stream->fh.length < len + 256) return len;
+ FS_fseek(&stream->fh, -(len + 256), SEEK_END);
+ FS_fread(buf, 1, 256, &stream->fh);
+ /* [0..9]: sync string, [30..255]: 0x20 */
+ if (memcmp(buf, syncstr, 10) != 0) {
+ return len;
+ }
+ #ifdef MMTAG_PARANOID
+ for (j = 30; j < 256; ++j) {
+ if (buf[j] != ' ') return len;
+ }
+ #endif
+ return len + 256; /* header is present. */
+}
+
+int mp3_skiptags(snd_stream_t *stream)
+{
+ unsigned char buf[128];
+ long len; size_t readsize;
+ int rc = -1;
+
+ readsize = FS_fread(buf, 1, 128, &stream->fh);
+ if (!readsize || FS_ferror(&stream->fh)) goto fail;
+
+ /* ID3v2 tag is at the start */
+ if (tag_is_id3v2(buf, readsize)) {
+ len = get_id3v2_len(buf, (long)readsize);
+ if (len >= stream->fh.length) goto fail;
+ stream->fh.start += len;
+ stream->fh.length -= len;
+ Con_DPrintf("MP3: skipped %ld bytes ID3v2 tag\n", len);
+ }
+ /* APE tag _might_ be at the start (discouraged
+ * but not forbidden, either.) read the header. */
+ else if (tag_is_apetag(buf, readsize)) {
+ len = get_ape_len(buf);
+ if (len >= stream->fh.length) goto fail;
+ stream->fh.start += len;
+ stream->fh.length -= len;
+ Con_DPrintf("MP3: skipped %ld bytes APE tag\n", len);
+ }
+
+ /* ID3v1 tag is at the end */
+ if (stream->fh.length >= 128) {
+ FS_fseek(&stream->fh, -128, SEEK_END);
+ readsize = FS_fread(buf, 1, 128, &stream->fh);
+ if (readsize != 128) goto fail;
+ if (tag_is_id3v1(buf, 128)) {
+ stream->fh.length -= 128;
+ Con_DPrintf("MP3: skipped %ld bytes ID3v1 tag\n", 128L);
+ /* FIXME: handle possible double-ID3v1 tags? */
+ }
+ }
+
+ /* do we know whether ape or lyrics3 is the first?
+ * well, we don't: we need to handle that later... */
+
+ /* check for the _old_ MusicMatch tag at end. */
+ if (stream->fh.length >= 68) {
+ FS_fseek(&stream->fh, -48, SEEK_END);
+ readsize = FS_fread(buf, 1, 48, &stream->fh);
+ if (readsize != 48) goto fail;
+ if (is_musicmatch(buf, 48)) {
+ len = get_musicmatch_len(stream);
+ if (len < 0) goto fail;
+ if (len >= stream->fh.length) goto fail;
+ stream->fh.length -= len;
+ Con_DPrintf("MP3: skipped %ld bytes MusicMatch tag\n", len);
+ }
+ }
+
+ /* APE tag may be at the end: read the footer */
+ if (stream->fh.length >= 32) {
+ FS_fseek(&stream->fh, -32, SEEK_END);
+ readsize = FS_fread(buf, 1, 32, &stream->fh);
+ if (readsize != 32) goto fail;
+ if (tag_is_apetag(buf, 32)) {
+ len = get_ape_len(buf);
+ if (len >= stream->fh.length) goto fail;
+ stream->fh.length -= len;
+ Con_DPrintf("MP3: skipped %ld bytes APE tag\n", len);
+ }
+ }
+
+ if (stream->fh.length >= 15) {
+ FS_fseek(&stream->fh, -15, SEEK_END);
+ readsize = FS_fread(buf, 1, 15, &stream->fh);
+ if (readsize != 15) goto fail;
+ len = is_lyrics3tag(buf, 15);
+ if (len == 2) {
+ len = get_lyrics3v2_len(buf, 6);
+ if (len >= stream->fh.length) goto fail;
+ if (len < 15) goto fail;
+ FS_fseek(&stream->fh, -len, SEEK_END);
+ readsize = FS_fread(buf, 1, 11, &stream->fh);
+ if (readsize != 11) goto fail;
+ if (!verify_lyrics3v2(buf, 11)) goto fail;
+ stream->fh.length -= len;
+ Con_DPrintf("MP3: skipped %ld bytes Lyrics3 tag\n", len);
+ }
+ else if (len == 1) {
+ len = get_lyrics3v1_len(stream);
+ if (len < 0) goto fail;
+ stream->fh.length -= len;
+ Con_DPrintf("MP3: skipped %ld bytes Lyrics3 tag\n", len);
+ }
+ }
+
+ rc = 0;
+ fail:
+ FS_rewind(&stream->fh);
+ return (stream->fh.length > 0)? rc : -1;
+}
+
+#endif /* USE_CODEC_MP3 */
diff --git a/Windows/CodeBlocks/QuakeSpasm-SDL2.cbp b/Windows/CodeBlocks/QuakeSpasm-SDL2.cbp
index 873cac46..f85ad23f 100644
--- a/Windows/CodeBlocks/QuakeSpasm-SDL2.cbp
+++ b/Windows/CodeBlocks/QuakeSpasm-SDL2.cbp
@@ -284,6 +284,9 @@
+
+
+
diff --git a/Windows/CodeBlocks/QuakeSpasm.cbp b/Windows/CodeBlocks/QuakeSpasm.cbp
index 36b0a7ba..b0fdf36d 100644
--- a/Windows/CodeBlocks/QuakeSpasm.cbp
+++ b/Windows/CodeBlocks/QuakeSpasm.cbp
@@ -283,6 +283,9 @@
+
+
+
diff --git a/Windows/VisualStudio/quakespasm-sdl2.vcproj b/Windows/VisualStudio/quakespasm-sdl2.vcproj
index e238f159..64b21656 100644
--- a/Windows/VisualStudio/quakespasm-sdl2.vcproj
+++ b/Windows/VisualStudio/quakespasm-sdl2.vcproj
@@ -561,6 +561,10 @@
RelativePath="..\..\Quake\snd_mp3.c"
>
+
+
diff --git a/Windows/VisualStudio/quakespasm.vcproj b/Windows/VisualStudio/quakespasm.vcproj
index fe5bed52..308e6ffe 100644
--- a/Windows/VisualStudio/quakespasm.vcproj
+++ b/Windows/VisualStudio/quakespasm.vcproj
@@ -561,6 +561,10 @@
RelativePath="..\..\Quake\snd_mp3.c"
>
+
+