[Scummvm-cvs-logs] SF.net SVN: scummvm: [25909] scummvm/trunk

fingolfin at users.sourceforge.net fingolfin at users.sourceforge.net
Wed Feb 28 15:48:27 CET 2007


Revision: 25909
          http://scummvm.svn.sourceforge.net/scummvm/?rev=25909&view=rev
Author:   fingolfin
Date:     2007-02-28 06:48:26 -0800 (Wed, 28 Feb 2007)

Log Message:
-----------
Changed the AppendableAudioStream code to use a queue of buffers, instead of a fixed size wrap-around memory buffer (this reduces memory usage in some cases by 500-700k, while actually being more flexible)

Modified Paths:
--------------
    scummvm/trunk/engines/kyra/vqa.cpp
    scummvm/trunk/engines/scumm/imuse_digi/dimuse.cpp
    scummvm/trunk/engines/scumm/imuse_digi/dimuse_sndmgr.cpp
    scummvm/trunk/engines/scumm/imuse_digi/dimuse_track.cpp
    scummvm/trunk/engines/scumm/smush/smush_mixer.cpp
    scummvm/trunk/engines/scumm/smush/smush_player.cpp
    scummvm/trunk/engines/sword1/sound.cpp
    scummvm/trunk/sound/audiostream.cpp
    scummvm/trunk/sound/audiostream.h

Modified: scummvm/trunk/engines/kyra/vqa.cpp
===================================================================
--- scummvm/trunk/engines/kyra/vqa.cpp	2007-02-28 14:07:47 UTC (rev 25908)
+++ scummvm/trunk/engines/kyra/vqa.cpp	2007-02-28 14:48:26 UTC (rev 25909)
@@ -268,8 +268,8 @@
 			_numPartialCodeBooks = 0;
 
 			if (_header.flags & 1) {
-				// A 2-second buffer ought to be enough
-				_stream = Audio::makeAppendableAudioStream(_header.freq, Audio::Mixer::FLAG_UNSIGNED, 2 * _header.freq * _header.channels);
+				// TODO/FIXME: Shouldn't we set FLAG_STEREO if _header.channels == 2 (wonders Fingolfin)
+				_stream = Audio::makeAppendableAudioStream(_header.freq, Audio::Mixer::FLAG_UNSIGNED);
 			} else {
 				_stream = NULL;
 			}
@@ -399,9 +399,10 @@
 		switch (tag) {
 		case MKID_BE('SND0'):	// Uncompressed sound
 			foundSound = true;
-			inbuf = (byte *)allocBuffer(0, size);
+			inbuf = new byte[size];
 			_file.read(inbuf, size);
-			_stream->append(inbuf, size);
+			assert(_stream);
+			_stream->queueBuffer(inbuf, size);
 			break;
 
 		case MKID_BE('SND1'):	// Compressed sound, almost like AUD
@@ -409,15 +410,18 @@
 			outsize = _file.readUint16LE();
 			insize = _file.readUint16LE();
 
-			inbuf = (byte *)allocBuffer(0, insize);
+			inbuf = new byte[insize];
 			_file.read(inbuf, insize);
 
 			if (insize == outsize) {
-				_stream->append(inbuf, insize);
+				assert(_stream);
+				_stream->queueBuffer(inbuf, insize);
 			} else {
-				outbuf = (byte *)allocBuffer(1, outsize);
+				outbuf = new byte[outsize];
 				decodeSND1(inbuf, insize, outbuf, outsize);
-				_stream->append(outbuf, outsize);
+				assert(_stream);
+				_stream->queueBuffer(outbuf, outsize);
+				delete[] inbuf;
 			}
 			break;
 
@@ -589,24 +593,25 @@
 
 			switch (tag) {
 			case MKID_BE('SND0'):	// Uncompressed sound
-				inbuf = (byte *)allocBuffer(0, size);
+				inbuf = new byte[size];
 				_file.read(inbuf, size);
-				_stream->append(inbuf, size);
+				_stream->queueBuffer(inbuf, size);
 				break;
 
 			case MKID_BE('SND1'):	// Compressed sound
 				outsize = _file.readUint16LE();
 				insize = _file.readUint16LE();
 
-				inbuf = (byte *)allocBuffer(0, insize);
+				inbuf = new byte[insize];
 				_file.read(inbuf, insize);
 
 				if (insize == outsize) {
-					_stream->append(inbuf, insize);
+					_stream->queueBuffer(inbuf, insize);
 				} else {
-					outbuf = (byte *)allocBuffer(1, outsize);
+					outbuf = new byte[outsize];
 					decodeSND1(inbuf, insize, outbuf, outsize);
-					_stream->append(outbuf, outsize);
+					_stream->queueBuffer(outbuf, outsize);
+					delete[] inbuf;
 				}
 				break;
 

Modified: scummvm/trunk/engines/scumm/imuse_digi/dimuse.cpp
===================================================================
--- scummvm/trunk/engines/scumm/imuse_digi/dimuse.cpp	2007-02-28 14:07:47 UTC (rev 25908)
+++ scummvm/trunk/engines/scumm/imuse_digi/dimuse.cpp	2007-02-28 14:48:26 UTC (rev 25909)
@@ -188,9 +188,8 @@
 				track->mixerFlags |= Audio::Mixer::FLAG_LITTLE_ENDIAN;
 #endif
 
-			int32 streamBufferSize = track->iteration;
 			track->stream2 = NULL;
-			track->stream = Audio::makeAppendableAudioStream(freq, track->mixerFlags, streamBufferSize);
+			track->stream = Audio::makeAppendableAudioStream(freq, track->mixerFlags);
 
 			const int pan = (track->pan != 64) ? 2 * track->pan - 127 : 0;
 			const int vol = track->vol / 1000;
@@ -324,10 +323,10 @@
 					if (_mixer->isReady()) {
 						_mixer->setChannelVolume(track->handle, vol);
 						_mixer->setChannelBalance(track->handle, pan);
-						track->stream->append(data, result);
+						track->stream->queueBuffer(data, result);
 						track->regionOffset += result;
-					}
-					free(data);
+					} else
+						delete[] data;
 
 					if (_sound->isEndOfRegion(track->soundHandle, track->curRegion)) {
 						switchToNextRegion(track);

Modified: scummvm/trunk/engines/scumm/imuse_digi/dimuse_sndmgr.cpp
===================================================================
--- scummvm/trunk/engines/scumm/imuse_digi/dimuse_sndmgr.cpp	2007-02-28 14:07:47 UTC (rev 25908)
+++ scummvm/trunk/engines/scumm/imuse_digi/dimuse_sndmgr.cpp	2007-02-28 14:48:26 UTC (rev 25909)
@@ -583,11 +583,11 @@
 	if ((soundHandle->bundle) && (!soundHandle->compressed)) {
 		size = soundHandle->bundle->decompressSampleByCurIndex(start + offset, size, buf, header_size, header_outside);
 	} else if (soundHandle->resPtr) {
-		*buf = (byte *)malloc(size);
+		*buf = new byte[size];
 		assert(*buf);
 		memcpy(*buf, soundHandle->resPtr + start + offset + header_size, size);
 	} else if ((soundHandle->bundle) && (soundHandle->compressed)) {
-		*buf = (byte *)malloc(size);
+		*buf = new byte[size];
 		assert(*buf);
 		char fileName[24];
 		sprintf(fileName, "%s_reg%03d", soundHandle->name, region);

Modified: scummvm/trunk/engines/scumm/imuse_digi/dimuse_track.cpp
===================================================================
--- scummvm/trunk/engines/scumm/imuse_digi/dimuse_track.cpp	2007-02-28 14:07:47 UTC (rev 25908)
+++ scummvm/trunk/engines/scumm/imuse_digi/dimuse_track.cpp	2007-02-28 14:48:26 UTC (rev 25909)
@@ -176,9 +176,8 @@
 			type = Audio::Mixer::kMusicSoundType;
 
 		// setup 1 second stream wrapped buffer
-		int32 streamBufferSize = track->iteration;
 		track->stream2 = NULL;
-		track->stream = Audio::makeAppendableAudioStream(freq, track->mixerFlags, streamBufferSize);
+		track->stream = Audio::makeAppendableAudioStream(freq, track->mixerFlags);
 		_mixer->playInputStream(type, &track->handle, track->stream, -1, vol, pan, false);
 		track->started = true;
 	}
@@ -356,8 +355,7 @@
 		type = Audio::Mixer::kMusicSoundType;
 
 	// setup 1 second stream wrapped buffer
-	int32 streamBufferSize = fadeTrack->iteration;
-	fadeTrack->stream = Audio::makeAppendableAudioStream(_sound->getFreq(fadeTrack->soundHandle), fadeTrack->mixerFlags, streamBufferSize);
+	fadeTrack->stream = Audio::makeAppendableAudioStream(_sound->getFreq(fadeTrack->soundHandle), fadeTrack->mixerFlags);
 	_mixer->playInputStream(type, &fadeTrack->handle, fadeTrack->stream, -1, fadeTrack->vol / 1000, fadeTrack->pan, false);
 	fadeTrack->started = true;
 	fadeTrack->used = true;

Modified: scummvm/trunk/engines/scumm/smush/smush_mixer.cpp
===================================================================
--- scummvm/trunk/engines/scumm/smush/smush_mixer.cpp	2007-02-28 14:07:47 UTC (rev 25908)
+++ scummvm/trunk/engines/scumm/smush/smush_mixer.cpp	2007-02-28 14:48:26 UTC (rev 25909)
@@ -105,6 +105,7 @@
 
 				_channels[i].chan->getParameters(stereo, is_16bit, vol, pan);
 
+				// Grab the audio data from the channel
 				int32 size = _channels[i].chan->getAvailableSoundDataSize();
 				byte *data = _channels[i].chan->getSoundData();
 
@@ -116,15 +117,16 @@
 				}
 
 				if (_mixer->isReady()) {
+					// Stream the data
 					if (!_channels[i].stream) {
-						_channels[i].stream = Audio::makeAppendableAudioStream(_channels[i].chan->getRate(), flags, 500000);
+						_channels[i].stream = Audio::makeAppendableAudioStream(_channels[i].chan->getRate(), flags);
 						_mixer->playInputStream(Audio::Mixer::kSFXSoundType, &_channels[i].handle, _channels[i].stream);
 					}
 					_mixer->setChannelVolume(_channels[i].handle, vol);
 					_mixer->setChannelBalance(_channels[i].handle, pan);
-					_channels[i].stream->append(data, size);
-				}
-				delete[] data;
+					_channels[i].stream->queueBuffer(data, size);	// The stream will free the buffer for us
+				} else
+					delete[] data;
 			}
 		}
 	}

Modified: scummvm/trunk/engines/scumm/smush/smush_player.cpp
===================================================================
--- scummvm/trunk/engines/scumm/smush/smush_player.cpp	2007-02-28 14:07:47 UTC (rev 25908)
+++ scummvm/trunk/engines/scumm/smush/smush_player.cpp	2007-02-28 14:48:26 UTC (rev 25909)
@@ -462,7 +462,7 @@
 			c->checkParameters(index, nbframes, size, track_flags, unknown);
 		c->appendData(b, bsize);
 	} else {
-		byte output_data[4096];
+		// TODO: Move this code into another SmushChannel subclass?
 		byte *src = (byte *)malloc(bsize);
 		b.read(src, bsize);
 		byte *d_src = src;
@@ -477,6 +477,8 @@
 					_IACTpos += bsize;
 					bsize = 0;
 				} else {
+					byte *output_data = new byte[4096];
+
 					memcpy(_IACToutput + _IACTpos, d_src, len);
 					byte *dst = output_data;
 					byte *d_src2 = _IACToutput;
@@ -507,10 +509,10 @@
 					} while (--count);
 
 					if (!_IACTstream) {
-						_IACTstream = Audio::makeAppendableAudioStream(22050, Audio::Mixer::FLAG_STEREO | Audio::Mixer::FLAG_16BITS, 900000);
+						_IACTstream = Audio::makeAppendableAudioStream(22050, Audio::Mixer::FLAG_STEREO | Audio::Mixer::FLAG_16BITS);
 						_vm->_mixer->playInputStream(Audio::Mixer::kSFXSoundType, &_IACTchannel, _IACTstream);
 					}
-					_IACTstream->append(output_data, 0x1000);
+					_IACTstream->queueBuffer(output_data, 0x1000);
 
 					bsize -= len;
 					d_src += len;

Modified: scummvm/trunk/engines/sword1/sound.cpp
===================================================================
--- scummvm/trunk/engines/sword1/sound.cpp	2007-02-28 14:07:47 UTC (rev 25908)
+++ scummvm/trunk/engines/sword1/sound.cpp	2007-02-28 14:48:26 UTC (rev 25909)
@@ -382,10 +382,8 @@
 }
 
 void Sound::closeCowSystem(void) {
-	if (_cowFile.isOpen())
-		_cowFile.close();
-	if (_cowHeader)
-		free(_cowHeader);
+	_cowFile.close();
+	free(_cowHeader);
 	_cowHeader = NULL;
 	_currentCowFile = 0;
 }

Modified: scummvm/trunk/sound/audiostream.cpp
===================================================================
--- scummvm/trunk/sound/audiostream.cpp	2007-02-28 14:07:47 UTC (rev 25908)
+++ scummvm/trunk/sound/audiostream.cpp	2007-02-28 14:48:26 UTC (rev 25909)
@@ -23,6 +23,7 @@
 #include "common/stdafx.h"
 #include "common/endian.h"
 #include "common/file.h"
+#include "common/list.h"
 #include "common/util.h"
 
 #include "sound/audiostream.h"
@@ -128,7 +129,6 @@
 	const int _rate;
 	const byte *_origPtr;
 
-	inline bool eosIntern() const	{ return _ptr >= _end; };
 public:
 	LinearMemoryStream(int rate, const byte *ptr, uint len, uint loopOffset, uint loopLen, bool autoFreeMemory)
 		: _ptr(ptr), _end(ptr+len), _loopPtr(0), _loopEnd(0), _rate(rate) {
@@ -154,7 +154,7 @@
 	int readBuffer(int16 *buffer, const int numSamples);
 
 	bool isStereo() const		{ return stereo; }
-	bool endOfData() const		{ return eosIntern(); }
+	bool endOfData() const		{ return _ptr >= _end; }
 
 	int getRate() const			{ return _rate; }
 };
@@ -162,7 +162,7 @@
 template<bool stereo, bool is16Bit, bool isUnsigned, bool isLE>
 int LinearMemoryStream<stereo, is16Bit, isUnsigned, isLE>::readBuffer(int16 *buffer, const int numSamples) {
 	int samples = 0;
-	while (samples < numSamples && !eosIntern()) {
+	while (samples < numSamples && _ptr < _end) {
 		const int len = MIN(numSamples, samples + (int)(_end - _ptr) / (is16Bit ? 2 : 1));
 		while (samples < len) {
 			*buffer++ = READ_ENDIAN_SAMPLE(is16Bit, isUnsigned, _ptr, isLE);
@@ -170,7 +170,7 @@
 			samples++;
 		}
 		// Loop, if looping was specified
-		if (_loopPtr && eosIntern()) {
+		if (_loopPtr && _ptr >= _end) {
 			_ptr = _loopPtr;
 			_end = _loopEnd;
 		}
@@ -228,6 +228,10 @@
 #pragma mark --- Appendable audio stream ---
 #pragma mark -
 
+struct Buffer {
+	byte *start;
+	byte *end;
+};
 
 /**
  * Wrapped memory stream.
@@ -235,18 +239,22 @@
 template<bool stereo, bool is16Bit, bool isUnsigned, bool isLE>
 class AppendableMemoryStream : public AppendableAudioStream {
 protected:
+
+	// A mutex to avoid access problems (causing e.g. corruption of
+	// the linked list) in thread aware environments.
 	Common::Mutex _mutex;
 
-	byte *_bufferStart;
-	byte *_bufferEnd;
-	byte *_pos;
-	byte *_end;
+	// List of all queueud buffers	
+	Common::List<Buffer> _bufferQueue;
+
+	// Position in the front buffer, if any
 	bool _finalized;
 	const int _rate;
+	byte *_pos;
 
-	inline bool eosIntern() const { return _end == _pos; };
+	inline bool eosIntern() const { return _bufferQueue.empty(); };
 public:
-	AppendableMemoryStream(int rate, uint bufferSize);
+	AppendableMemoryStream(int rate);
 	~AppendableMemoryStream();
 	int readBuffer(int16 *buffer, const int numSamples);
 
@@ -256,30 +264,22 @@
 
 	int getRate() const			{ return _rate; }
 
-	void append(const byte *data, uint32 len);
+	void queueBuffer(byte *data, uint32 size);
 	void finish()				{ _finalized = true; }
 };
 
 template<bool stereo, bool is16Bit, bool isUnsigned, bool isLE>
-AppendableMemoryStream<stereo, is16Bit, isUnsigned, isLE>::AppendableMemoryStream(int rate, uint bufferSize)
- : _finalized(false), _rate(rate) {
+AppendableMemoryStream<stereo, is16Bit, isUnsigned, isLE>::AppendableMemoryStream(int rate)
+ : _finalized(false), _rate(rate), _pos(0) {
 
-	// Verify the buffer size is sane
-	if (is16Bit && stereo)
-		assert((bufferSize & 3) == 0);
-	else if (is16Bit || stereo)
-		assert((bufferSize & 1) == 0);
-
-	_bufferStart = (byte *)malloc(bufferSize);
-	assert(_bufferStart != NULL);
-
-	_pos = _end = _bufferStart;
-	_bufferEnd = _bufferStart + bufferSize;
 }
 
 template<bool stereo, bool is16Bit, bool isUnsigned, bool isLE>
 AppendableMemoryStream<stereo, is16Bit, isUnsigned, isLE>::~AppendableMemoryStream() {
-	free(_bufferStart);
+	// Clear the queue
+	Common::List<Buffer>::iterator iter;
+	for (iter = _bufferQueue.begin(); iter != _bufferQueue.end(); ++iter)
+		delete[] iter->start;
 }
 
 template<bool stereo, bool is16Bit, bool isUnsigned, bool isLE>
@@ -288,12 +288,19 @@
 
 	int samples = 0;
 	while (samples < numSamples && !eosIntern()) {
-		// Wrap around?
-		if (_pos >= _bufferEnd)
-			_pos = _pos - (_bufferEnd - _bufferStart);
+		Buffer buf = *_bufferQueue.begin();
+		if (_pos == 0)
+			_pos = buf.start;
 
-		const byte *endMarker = (_pos > _end) ? _bufferEnd : _end;
-		const int len = MIN(numSamples, samples + (int)(endMarker - _pos) / (is16Bit ? 2 : 1));
+		assert(buf.start <= _pos && _pos <= buf.end);
+		const int samplesLeftInCurBuffer = buf.end - _pos;
+		if (samplesLeftInCurBuffer == 0) {
+			_bufferQueue.erase(_bufferQueue.begin());
+			_pos = 0;
+			continue;
+		}
+
+		const int len = MIN(numSamples, samples + samplesLeftInCurBuffer / (is16Bit ? 2 : 1));
 		while (samples < len) {
 			*buffer++ = READ_ENDIAN_SAMPLE(is16Bit, isUnsigned, _pos, isLE);
 			_pos += (is16Bit ? 2 : 1);
@@ -305,50 +312,45 @@
 }
 
 template<bool stereo, bool is16Bit, bool isUnsigned, bool isLE>
-void AppendableMemoryStream<stereo, is16Bit, isUnsigned, isLE>::append(const byte *data, uint32 len) {
+void AppendableMemoryStream<stereo, is16Bit, isUnsigned, isLE>::queueBuffer(byte *data, uint32 size) {
 	Common::StackLock lock(_mutex);
 
 	// Verify the buffer size is sane
 	if (is16Bit && stereo)
-		assert((len & 3) == 0);
+		assert((size & 3) == 0);
 	else if (is16Bit || stereo)
-		assert((len & 1) == 0);
+		assert((size & 1) == 0);
 
 	// Verify that the stream has not yet been finalized (by a call to finish())
 	assert(!_finalized);
 
-	if (_end + len > _bufferEnd) {
-		// Wrap-around case
-		uint32 size_to_end_of_buffer = _bufferEnd - _end;
-		len -= size_to_end_of_buffer;
-		if ((_end < _pos) || (_bufferStart + len >= _pos)) {
-			debug(2, "AppendableMemoryStream: buffer overflow (A)");
-			return;
-		}
-		memcpy(_end, data, size_to_end_of_buffer);
-		memcpy(_bufferStart, data + size_to_end_of_buffer, len);
-		_end = _bufferStart + len;
-	} else {
-		if ((_end < _pos) && (_end + len >= _pos)) {
-			debug(2, "AppendableMemoryStream: buffer overflow (B)");
-			return;
-		}
-		memcpy(_end, data, len);
-		_end += len;
-	}
+	// Queue the buffer
+	Buffer buf = {data, data+size};
+	_bufferQueue.push_back(buf);
+
+
+#if 0
+	// Output some stats
+	uint totalSize = 0;
+	Common::List<Buffer>::iterator iter;
+	for (iter = _bufferQueue.begin(); iter != _bufferQueue.end(); ++iter)
+		totalSize += iter->end - iter->start;
+	printf("AppendableMemoryStream::queueBuffer: added a %d byte buf, a total of %d bytes are queued\n",
+				size, totalSize);
+#endif
 }
 
 
 #define MAKE_WRAPPED(STEREO, UNSIGNED) \
 		if (is16Bit) { \
 			if (isLE) \
-				return new AppendableMemoryStream<STEREO, true, UNSIGNED, true>(rate, len); \
+				return new AppendableMemoryStream<STEREO, true, UNSIGNED, true>(rate); \
 			else  \
-				return new AppendableMemoryStream<STEREO, true, UNSIGNED, false>(rate, len); \
+				return new AppendableMemoryStream<STEREO, true, UNSIGNED, false>(rate); \
 		} else \
-			return new AppendableMemoryStream<STEREO, false, UNSIGNED, false>(rate, len)
+			return new AppendableMemoryStream<STEREO, false, UNSIGNED, false>(rate)
 
-AppendableAudioStream *makeAppendableAudioStream(int rate, byte _flags, uint32 len) {
+AppendableAudioStream *makeAppendableAudioStream(int rate, byte _flags) {
 	const bool isStereo = (_flags & Audio::Mixer::FLAG_STEREO) != 0;
 	const bool is16Bit = (_flags & Audio::Mixer::FLAG_16BITS) != 0;
 	const bool isUnsigned = (_flags & Audio::Mixer::FLAG_UNSIGNED) != 0;

Modified: scummvm/trunk/sound/audiostream.h
===================================================================
--- scummvm/trunk/sound/audiostream.h	2007-02-28 14:07:47 UTC (rev 25908)
+++ scummvm/trunk/sound/audiostream.h	2007-02-28 14:48:26 UTC (rev 25909)
@@ -31,7 +31,8 @@
 namespace Audio {
 
 /**
- * Generic input stream for the resampling code.
+ * Generic audio input stream. Subclasses of this are used to feed arbitrary
+ * sampled audio data into ScummVM's audio mixer.
  */
 class AudioStream {
 public:
@@ -89,6 +90,13 @@
 	static AudioStream* openStreamFile(const char *filename);
 };
 
+/**
+ * Factory function for a raw linear AudioStream, which will simply treat all data
+ * in the buffer described by ptr and len as raw sample data in the specified
+ * format. It will then simply pass this data directly to the mixer, after converting
+ * it to the sample format used by the mixer (i.e. 16 bit signed native endian).
+ * Optionally supports (infinite) looping of a portion of the data.
+ */
 AudioStream *makeLinearInputStream(int rate, byte flags, const byte *ptr, uint32 len, uint loopOffset, uint loopLen);
 
 /**
@@ -97,11 +105,29 @@
  */
 class AppendableAudioStream : public Audio::AudioStream {
 public:
-	virtual void append(const byte *data, uint32 len) = 0;
+	
+	/**
+	 * Queue another audio data buffer for playback. The stream
+	 * will playback all queued buffers, in the order they were
+	 * queued. After all data contained in them has been played,
+	 * the buffer will be delete[]'d (so make sure to allocate them
+	 * with new[], not with malloc).
+	 */
+	virtual void queueBuffer(byte *data, uint32 size) = 0;
+	
+	/**
+	 * Mark the stream as finished, that is, signal that no further data
+	 * will be appended to it. Only after this has been done can the
+	 * AppendableAudioStream ever 'end' (
+	 */
 	virtual void finish() = 0;
 };
 
-AppendableAudioStream *makeAppendableAudioStream(int rate, byte _flags, uint32 len);
+/**
+ * Factory function for an AppendableAudioStream. The rate and flags
+ * parameters are analog to those used in makeLinearInputStream.
+ */
+AppendableAudioStream *makeAppendableAudioStream(int rate, byte flags);
 
 
 } // End of namespace Audio


This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.




More information about the Scummvm-git-logs mailing list