1 /*
2  ** i_music.cpp
3  ** Plays music
4  **
5  **---------------------------------------------------------------------------
6  ** Copyright 1998-2016 Randy Heit
7  ** Copyright 2005-2019 Christoph Oelckers
8  ** All rights reserved.
9  **
10  ** Redistribution and use in source and binary forms, with or without
11  ** modification, are permitted provided that the following conditions
12  ** are met:
13  **
14  ** 1. Redistributions of source code must retain the above copyright
15  **    notice, this list of conditions and the following disclaimer.
16  ** 2. Redistributions in binary form must reproduce the above copyright
17  **    notice, this list of conditions and the following disclaimer in the
18  **    documentation and/or other materials provided with the distribution.
19  ** 3. The name of the author may not be used to endorse or promote products
20  **    derived from this software without specific prior written permission.
21  **
22  ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
23  ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
24  ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
25  ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
26  ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
27  ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28  ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29  ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30  ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
31  ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32  **---------------------------------------------------------------------------
33  **
34  */
35 
36 #include <stdint.h>
37 #include <vector>
38 #include <string>
39 #include <zlib.h>
40 #include "m_swap.h"
41 #include "zmusic_internal.h"
42 #include "midiconfig.h"
43 #include "musinfo.h"
44 #include "streamsources/streamsource.h"
45 #include "midisources/midisource.h"
46 #include "critsec.h"
47 
48 #define GZIP_ID1		31
49 #define GZIP_ID2		139
50 #define GZIP_CM			8
51 #define GZIP_ID			MAKE_ID(GZIP_ID1,GZIP_ID2,GZIP_CM,0)
52 
53 #define GZIP_FTEXT		1
54 #define GZIP_FHCRC		2
55 #define GZIP_FEXTRA		4
56 #define GZIP_FNAME		8
57 #define GZIP_FCOMMENT	16
58 
59 class MIDIDevice;
60 class OPLmusicFile;
61 class StreamSource;
62 class MusInfo;
63 
64 MusInfo *OpenStreamSong(StreamSource *source);
65 const char *GME_CheckFormat(uint32_t header);
66 MusInfo* CDDA_OpenSong(MusicIO::FileInterface* reader);
67 MusInfo* CD_OpenSong(int track, int id);
68 MusInfo* CreateMIDIStreamer(MIDISource *source, EMidiDevice devtype, const char* args);
69 
70 //==========================================================================
71 //
72 // ungzip
73 //
74 // VGZ files are compressed with gzip, so we need to uncompress them before
75 // handing them to GME.
76 //
77 //==========================================================================
78 
ungzip(uint8_t * data,int complen,std::vector<uint8_t> & newdata)79 static bool ungzip(uint8_t *data, int complen, std::vector<uint8_t> &newdata)
80 {
81 	const uint8_t *max = data + complen - 8;
82 	const uint8_t *compstart = data + 10;
83 	uint8_t flags = data[3];
84 	unsigned isize;
85 	z_stream stream;
86 	int err;
87 
88 	// Find start of compressed data stream
89 	if (flags & GZIP_FEXTRA)
90 	{
91 		compstart += 2 + LittleShort(*(uint16_t *)(data + 10));
92 	}
93 	if (flags & GZIP_FNAME)
94 	{
95 		while (compstart < max && *compstart != 0)
96 		{
97 			compstart++;
98 		}
99 	}
100 	if (flags & GZIP_FCOMMENT)
101 	{
102 		while (compstart < max && *compstart != 0)
103 		{
104 			compstart++;
105 		}
106 	}
107 	if (flags & GZIP_FHCRC)
108 	{
109 		compstart += 2;
110 	}
111 	if (compstart >= max - 1)
112 	{
113 		return false;
114 	}
115 
116 	// Decompress
117 	isize = LittleLong(*(uint32_t *)(data + complen - 4));
118 	newdata.resize(isize);
119 
120 	stream.next_in = (Bytef *)compstart;
121 	stream.avail_in = (uInt)(max - compstart);
122 	stream.next_out = &newdata[0];
123 	stream.avail_out = isize;
124 	stream.zalloc = (alloc_func)0;
125 	stream.zfree = (free_func)0;
126 
127 	err = inflateInit2(&stream, -MAX_WBITS);
128 	if (err != Z_OK)
129 	{
130 		return false;
131 	}
132 	err = inflate(&stream, Z_FINISH);
133 	if (err != Z_STREAM_END)
134 	{
135 		inflateEnd(&stream);
136 		return false;
137 	}
138 	err = inflateEnd(&stream);
139 	if (err != Z_OK)
140 	{
141 		return false;
142 	}
143 	return true;
144 }
145 
146 
147 //==========================================================================
148 //
149 // identify a music lump's type and set up a player for it
150 //
151 //==========================================================================
152 
ZMusic_OpenSongInternal(MusicIO::FileInterface * reader,EMidiDevice device,const char * Args)153 static  MusInfo *ZMusic_OpenSongInternal (MusicIO::FileInterface *reader, EMidiDevice device, const char *Args)
154 {
155 	MusInfo *info = nullptr;
156 	StreamSource *streamsource = nullptr;
157 	const char *fmt;
158 	uint32_t id[32/4];
159 
160 	if(reader->read(id, 32) != 32 || reader->seek(-32, SEEK_CUR) != 0)
161 	{
162 		SetError("Unable to read header");
163 		reader->close();
164 		return nullptr;
165 	}
166 	try
167 	{
168 		// Check for gzip compression. Some formats are expected to have players
169 		// that can handle it, so it simplifies things if we make all songs
170 		// gzippable.
171 		if ((id[0] & MAKE_ID(255, 255, 255, 0)) == GZIP_ID)
172 		{
173 			// swap out the reader with one that reads the decompressed content.
174 			auto zreader = new MusicIO::VectorReader([reader](std::vector<uint8_t>& array)
175 													 {
176 														 bool res = false;
177 														 auto len = reader->filelength();
178 														 uint8_t* gzipped = new uint8_t[len];
179 														 if (reader->read(gzipped, len) == len)
180 														 {
181 															 res = ungzip(gzipped, (int)len, array);
182 														 }
183 														 delete[] gzipped;
184 													 });
185 			reader->close();
186 			reader = zreader;
187 
188 
189 			if (reader->read(id, 32) != 32 || reader->seek(-32, SEEK_CUR) != 0)
190 			{
191 				reader->close();
192 				return nullptr;
193 			}
194 		}
195 
196 		EMIDIType miditype = ZMusic_IdentifyMIDIType(id, sizeof(id));
197 		if (miditype != MIDI_NOTMIDI)
198 		{
199 			std::vector<uint8_t> data(reader->filelength());
200 			if (reader->read(data.data(), (long)data.size()) != (long)data.size())
201 			{
202 				SetError("Failed to read MIDI data");
203 				reader->close();
204 				return nullptr;
205 			}
206 			auto source = ZMusic_CreateMIDISource(data.data(), data.size(), miditype);
207 			if (source == nullptr)
208 			{
209 				reader->close();
210 				return nullptr;
211 			}
212 			if (!source->isValid())
213 			{
214 				SetError("Invalid data in MIDI file");
215 				delete source;
216 				return nullptr;
217 			}
218 
219 #ifndef HAVE_SYSTEM_MIDI
220 			// some platforms don't support MDEV_STANDARD so map to MDEV_SNDSYS
221 			if (device == MDEV_STANDARD)
222 				device = MDEV_SNDSYS;
223 #endif
224 
225 			info = CreateMIDIStreamer(source, device, Args? Args : "");
226 		}
227 
228 		// Check for CDDA "format"
229 		else if ((id[0] == MAKE_ID('R', 'I', 'F', 'F') && id[2] == MAKE_ID('C', 'D', 'D', 'A')))
230 		{
231 			// This is a CDDA file
232 			info = CDDA_OpenSong(reader);
233 		}
234 
235 		// Check for various raw OPL formats
236 		else
237 		{
238 #ifdef HAVE_OPL
239 			if (
240 				(id[0] == MAKE_ID('R', 'A', 'W', 'A') && id[1] == MAKE_ID('D', 'A', 'T', 'A')) ||		// Rdos Raw OPL
241 				(id[0] == MAKE_ID('D', 'B', 'R', 'A') && id[1] == MAKE_ID('W', 'O', 'P', 'L')) ||		// DosBox Raw OPL
242 				(id[0] == MAKE_ID('A', 'D', 'L', 'I') && *((uint8_t*)id + 4) == 'B'))		// Martin Fernandez's modified IMF
243 			{
244 				streamsource = OPL_OpenSong(reader, &oplConfig);
245 
246 			}
247 			else
248 #endif
249 				if ((id[0] == MAKE_ID('R', 'I', 'F', 'F') && id[2] == MAKE_ID('C', 'D', 'X', 'A')))
250 			{
251 				streamsource = XA_OpenSong(reader);	// this takes over the reader.
252 				reader = nullptr;					// We do not own this anymore.
253 			}
254 			// Check for game music
255 			else if ((fmt = GME_CheckFormat(id[0])) != nullptr && fmt[0] != '\0')
256 			{
257 				streamsource = GME_OpenSong(reader, fmt, miscConfig.snd_outputrate);
258 			}
259 			// Check for module formats
260 			else
261 			{
262 				streamsource = MOD_OpenSong(reader, miscConfig.snd_outputrate);
263 			}
264 			if (streamsource == nullptr)
265 			{
266 				streamsource = SndFile_OpenSong(reader);		// this only takes over the reader if it succeeds. We need to look out for this.
267 				if (streamsource != nullptr) reader = nullptr;
268 			}
269 
270 			if (streamsource)
271 			{
272 				info = OpenStreamSong(streamsource);
273 			}
274 		}
275 
276 		if (!info)
277 		{
278 			// File could not be identified as music.
279 			if (reader) reader->close();
280 			SetError("Unable to identify as music");
281 			return nullptr;
282 		}
283 
284 		if (info && !info->IsValid())
285 		{
286 			delete info;
287 			SetError("Unable to identify as music");
288 			info = nullptr;
289 		}
290 		if (reader) reader->close();
291 		return info;
292 	}
293 	catch (const std::exception &ex)
294 	{
295 		// Make sure the reader is closed if this function abnormally terminates
296 		if (reader) reader->close();
297 		SetError(ex.what());
298 		return nullptr;
299 	}
300 }
301 
ZMusic_OpenSongFile(const char * filename,EMidiDevice device,const char * Args)302 DLL_EXPORT ZMusic_MusicStream ZMusic_OpenSongFile(const char* filename, EMidiDevice device, const char* Args)
303 {
304 	auto f = MusicIO::utf8_fopen(filename, "rb");
305 	if (!f)
306 	{
307 		SetError("File not found");
308 		return nullptr;
309 	}
310 	auto fr = new MusicIO::StdioFileReader;
311 	fr->f = f;
312 	return ZMusic_OpenSongInternal(fr, device, Args);
313 }
314 
ZMusic_OpenSongMem(const void * mem,size_t size,EMidiDevice device,const char * Args)315 DLL_EXPORT ZMusic_MusicStream ZMusic_OpenSongMem(const void* mem, size_t size, EMidiDevice device, const char* Args)
316 {
317 	if (!mem || !size)
318 	{
319 		SetError("Invalid data");
320 		return nullptr;
321 	}
322 	// Data must be copied because it may be used as a streaming source and we cannot guarantee that the client memory stays valid. We also have no means to free it.
323 	auto mr = new MusicIO::VectorReader((uint8_t*)mem, (long)size);
324 	return ZMusic_OpenSongInternal(mr, device, Args);
325 }
326 
ZMusic_OpenSong(ZMusicCustomReader * reader,EMidiDevice device,const char * Args)327 DLL_EXPORT ZMusic_MusicStream ZMusic_OpenSong(ZMusicCustomReader* reader, EMidiDevice device, const char* Args)
328 {
329 	if (!reader)
330 	{
331 		SetError("No reader protocol specified");
332 		return nullptr;
333 	}
334 	auto cr = new CustomFileReader(reader);	// Oh no! We just put another wrapper around the client's wrapper!
335 	return ZMusic_OpenSongInternal(cr, device, Args);
336 }
337 
338 
339 //==========================================================================
340 //
341 // play CD music
342 //
343 //==========================================================================
344 
ZMusic_OpenCDSong(int track,int id)345 DLL_EXPORT MusInfo *ZMusic_OpenCDSong (int track, int id)
346 {
347 	MusInfo *info = CD_OpenSong (track, id);
348 
349 	if (info && !info->IsValid ())
350 	{
351 		delete info;
352 		info = nullptr;
353 		SetError("Unable to open CD Audio");
354 	}
355 
356 	return info;
357 }
358 
359 //==========================================================================
360 //
361 // streaming callback
362 //
363 //==========================================================================
364 
ZMusic_FillStream(MusInfo * song,void * buff,int len)365 DLL_EXPORT zmusic_bool ZMusic_FillStream(MusInfo* song, void* buff, int len)
366 {
367 	if (song == nullptr) return false;
368 	std::lock_guard<FCriticalSection> lock(song->CritSec);
369 	return song->ServiceStream(buff, len);
370 }
371 
372 //==========================================================================
373 //
374 // starts playback
375 //
376 //==========================================================================
377 
ZMusic_Start(MusInfo * song,int subsong,zmusic_bool loop)378 DLL_EXPORT zmusic_bool ZMusic_Start(MusInfo *song, int subsong, zmusic_bool loop)
379 {
380 	if (!song) return true;	// Starting a null song is not an error! It just won't play anything.
381 	try
382 	{
383 		song->Play(loop, subsong);
384 		return true;
385 	}
386 	catch (const std::exception & ex)
387 	{
388 		SetError(ex.what());
389 		return false;
390 	}
391 }
392 
393 //==========================================================================
394 //
395 // Utilities
396 //
397 //==========================================================================
398 
ZMusic_Pause(MusInfo * song)399 DLL_EXPORT void ZMusic_Pause(MusInfo *song)
400 {
401 	if (!song) return;
402 	song->Pause();
403 }
404 
ZMusic_Resume(MusInfo * song)405 DLL_EXPORT void ZMusic_Resume(MusInfo *song)
406 {
407 	if (!song) return;
408 	song->Resume();
409 }
410 
ZMusic_Update(MusInfo * song)411 DLL_EXPORT void ZMusic_Update(MusInfo *song)
412 {
413 	if (!song) return;
414 	song->Update();
415 }
416 
ZMusic_IsPlaying(MusInfo * song)417 DLL_EXPORT zmusic_bool ZMusic_IsPlaying(MusInfo *song)
418 {
419 	if (!song) return false;
420 	return song->IsPlaying();
421 }
422 
ZMusic_Stop(MusInfo * song)423 DLL_EXPORT void ZMusic_Stop(MusInfo *song)
424 {
425 	if (!song) return;
426 	std::lock_guard<FCriticalSection> lock(song->CritSec);
427 	song->Stop();
428 }
429 
ZMusic_SetSubsong(MusInfo * song,int subsong)430 DLL_EXPORT zmusic_bool ZMusic_SetSubsong(MusInfo *song, int subsong)
431 {
432 	if (!song) return false;
433 	std::lock_guard<FCriticalSection> lock(song->CritSec);
434 	return song->SetSubsong(subsong);
435 }
436 
ZMusic_IsLooping(MusInfo * song)437 DLL_EXPORT zmusic_bool ZMusic_IsLooping(MusInfo *song)
438 {
439 	if (!song) return false;
440 	return song->m_Looping;
441 }
442 
ZMusic_GetDeviceType(MusInfo * song)443 DLL_EXPORT int ZMusic_GetDeviceType(MusInfo* song)
444 {
445 	if (!song) return false;
446 	return song->GetDeviceType();
447 }
448 
ZMusic_IsMIDI(MusInfo * song)449 DLL_EXPORT zmusic_bool ZMusic_IsMIDI(MusInfo *song)
450 {
451 	if (!song) return false;
452 	return song->IsMIDI();
453 }
454 
ZMusic_GetStreamInfo(MusInfo * song,SoundStreamInfo * fmt)455 DLL_EXPORT void ZMusic_GetStreamInfo(MusInfo *song, SoundStreamInfo *fmt)
456 {
457 	if (!fmt) return;
458 	if (!song) *fmt = {};
459 	std::lock_guard<FCriticalSection> lock(song->CritSec);
460 	*fmt = song->GetStreamInfo();
461 }
462 
ZMusic_Close(MusInfo * song)463 DLL_EXPORT void ZMusic_Close(MusInfo *song)
464 {
465 	if (!song) return;
466 	delete song;
467 }
468 
ZMusic_VolumeChanged(MusInfo * song)469 DLL_EXPORT void ZMusic_VolumeChanged(MusInfo *song)
470 {
471 	if (!song) return;
472 	std::lock_guard<FCriticalSection> lock(song->CritSec);
473 	song->MusicVolumeChanged();
474 }
475 
476 static std::string staticErrorMessage;
477 
ZMusic_GetStats(MusInfo * song)478 DLL_EXPORT const char *ZMusic_GetStats(MusInfo *song)
479 {
480 	if (!song) return "";
481 	std::lock_guard<FCriticalSection> lock(song->CritSec);
482 	staticErrorMessage = song->GetStats();
483 	return staticErrorMessage.c_str();
484 }
485 
SetError(const char * msg)486 void SetError(const char* msg)
487 {
488 	staticErrorMessage = msg;
489 }
490 
ZMusic_GetLastError()491 DLL_EXPORT const char* ZMusic_GetLastError()
492 {
493 	return staticErrorMessage.c_str();
494 }
495 
ZMusic_WriteSMF(MIDISource * source,const char * fn,int looplimit)496 DLL_EXPORT zmusic_bool ZMusic_WriteSMF(MIDISource* source, const char *fn, int looplimit)
497 {
498 	std::vector<uint8_t> midi;
499 	bool success;
500 
501 	if (!source) return false;
502 	source->CreateSMF(midi, 1);
503 	auto f = MusicIO::utf8_fopen(fn, "wt");
504 	if (f == nullptr) return false;
505 	success = (fwrite(&midi[0], 1, midi.size(), f) == midi.size());
506 	fclose(f);
507 	return success;
508 }
509