diff --git a/source/soundmanager/data/ogg.cpp b/source/soundmanager/data/ogg.cpp
new file mode 100644
index 0000000000..7c692c3ae5
--- /dev/null
+++ b/source/soundmanager/data/ogg.cpp
@@ -0,0 +1,323 @@
+/* Copyright (C) 2012 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. 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.
+ *
+ * 0 A.D. 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 0 A.D. If not, see .
+ */
+#include "precompiled.h"
+
+#include "ogg.h"
+
+#if CONFIG2_AUDIO
+
+#include "lib/external_libraries/openal.h"
+#include "lib/external_libraries/vorbis.h"
+
+#include "lib/byte_order.h"
+#include "lib/file/io/io.h"
+#include "lib/file/file_system.h"
+
+#include "lib/file/vfs/vfs_util.h"
+#include "ps/Filesystem.h"
+
+
+static Status LibErrorFromVorbis(int err)
+{
+ switch(err)
+ {
+ case 0:
+ return INFO::OK;
+ case OV_HOLE:
+ return ERR::AGAIN;
+ case OV_EREAD:
+ return ERR::IO;
+ case OV_EFAULT:
+ return ERR::LOGIC;
+ case OV_EIMPL:
+ return ERR::NOT_SUPPORTED;
+ case OV_EINVAL:
+ return ERR::INVALID_PARAM;
+ case OV_ENOTVORBIS:
+ return ERR::NOT_SUPPORTED;
+ case OV_EBADHEADER:
+ return ERR::CORRUPTED;
+ case OV_EVERSION:
+ return ERR::INVALID_VERSION;
+ case OV_ENOTAUDIO:
+ return ERR::_1;
+ case OV_EBADPACKET:
+ return ERR::_2;
+ case OV_EBADLINK:
+ return ERR::_3;
+ case OV_ENOSEEK:
+ return ERR::_4;
+ default:
+ return ERR::FAIL;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+
+class VorbisFileAdapter
+{
+public:
+ VorbisFileAdapter(const PFile& openedFile)
+ : file(openedFile)
+ , size(FileSize(openedFile->Pathname()))
+ , offset(0)
+ {
+ }
+
+ static size_t Read(void* bufferToFill, size_t itemSize, size_t numItems, void* context)
+ {
+ VorbisFileAdapter* adapter = (VorbisFileAdapter*)context;
+ const off_t sizeRequested = numItems*itemSize;
+ const off_t sizeRemaining = adapter->size - adapter->offset;
+ const size_t sizeToRead = (size_t)std::min(sizeRequested, sizeRemaining);
+
+ io::Operation op(*adapter->file.get(), bufferToFill, sizeToRead, adapter->offset);
+ if(io::Run(op) == INFO::OK)
+ {
+ adapter->offset += sizeToRead;
+ return sizeToRead;
+ }
+
+ errno = EIO;
+ return 0;
+ }
+
+ static int Seek(void* context, ogg_int64_t offset, int whence)
+ {
+ VorbisFileAdapter* adapter = (VorbisFileAdapter*)context;
+
+ off_t origin = 0;
+ switch(whence)
+ {
+ case SEEK_SET:
+ origin = 0;
+ break;
+ case SEEK_CUR:
+ origin = adapter->offset;
+ break;
+ case SEEK_END:
+ origin = adapter->size+1;
+ break;
+ NODEFAULT;
+ }
+
+ adapter->offset = Clamp(off_t(origin+offset), off_t(0), adapter->size);
+ return 0;
+ }
+
+ static int Close(void* context)
+ {
+ VorbisFileAdapter* adapter = (VorbisFileAdapter*)context;
+ adapter->file.reset();
+ return 0; // return value is ignored
+ }
+
+ static long Tell(void* context)
+ {
+ VorbisFileAdapter* adapter = (VorbisFileAdapter*)context;
+ return adapter->offset;
+ }
+
+private:
+ PFile file;
+ off_t size;
+ off_t offset;
+};
+
+//-----------------------------------------------------------------------------
+
+class VorbisBufferAdapter
+{
+public:
+ VorbisBufferAdapter(const shared_ptr& buffer, size_t size)
+ : buffer(buffer)
+ , size(size)
+ , offset(0)
+ {
+ }
+
+ static size_t Read(void* bufferToFill, size_t itemSize, size_t numItems, void* context)
+ {
+ VorbisBufferAdapter* adapter = (VorbisBufferAdapter*)context;
+ const off_t sizeRequested = numItems*itemSize;
+ const off_t sizeRemaining = adapter->size - adapter->offset;
+ const size_t sizeToRead = (size_t)std::min(sizeRequested, sizeRemaining);
+
+ memcpy(bufferToFill, adapter->buffer.get() + adapter->offset, sizeToRead);
+
+ adapter->offset += sizeToRead;
+ return sizeToRead;
+ }
+
+ static int Seek(void* context, ogg_int64_t offset, int whence)
+ {
+ VorbisBufferAdapter* adapter = (VorbisBufferAdapter*)context;
+
+ off_t origin = 0;
+ switch(whence)
+ {
+ case SEEK_SET:
+ origin = 0;
+ break;
+ case SEEK_CUR:
+ origin = adapter->offset;
+ break;
+ case SEEK_END:
+ origin = adapter->size+1;
+ break;
+ NODEFAULT;
+ }
+
+ adapter->offset = Clamp(off_t(origin+offset), off_t(0), adapter->size);
+ return 0;
+ }
+
+ static int Close(void* context)
+ {
+ VorbisBufferAdapter* adapter = (VorbisBufferAdapter*)context;
+ adapter->buffer.reset();
+ return 0; // return value is ignored
+ }
+
+ static long Tell(void* context)
+ {
+ VorbisBufferAdapter* adapter = (VorbisBufferAdapter*)context;
+ return adapter->offset;
+ }
+
+private:
+ shared_ptr buffer;
+ off_t size;
+ off_t offset;
+};
+
+
+//-----------------------------------------------------------------------------
+
+template
+class OggStreamImpl : public OggStream
+{
+public:
+ OggStreamImpl(const Adapter& adapter)
+ : adapter(adapter)
+ {
+ m_fileEOF = false;
+ }
+
+ Status Open()
+ {
+ ov_callbacks callbacks;
+ callbacks.read_func = Adapter::Read;
+ callbacks.close_func = Adapter::Close;
+ callbacks.seek_func = Adapter::Seek;
+ callbacks.tell_func = Adapter::Tell;
+ const int ret = ov_open_callbacks(&adapter, &vf, 0, 0, callbacks);
+ if(ret != 0)
+ WARN_RETURN(LibErrorFromVorbis(ret));
+
+ const int link = -1; // retrieve info for current bitstream
+ info = ov_info(&vf, link);
+ if(!info)
+ WARN_RETURN(ERR::INVALID_HANDLE);
+
+ return INFO::OK;
+ }
+
+ virtual ALenum Format()
+ {
+ return (info->channels == 1)? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
+ }
+
+ virtual ALsizei SamplingRate()
+ {
+ return info->rate;
+ }
+ virtual bool atFileEOF()
+ {
+ return m_fileEOF;
+ }
+
+ virtual Status ResetFile()
+ {
+ ov_time_seek( &vf, 0 );
+ m_fileEOF = false;
+ return INFO::OK;
+ }
+
+ virtual Status GetNextChunk(u8* buffer, size_t size)
+ {
+ // we may have to call ov_read multiple times because it
+ // treats the buffer size "as a limit and not a request"
+ size_t bytesRead = 0;
+ for(;;)
+ {
+ const int isBigEndian = (BYTE_ORDER == BIG_ENDIAN);
+ const int wordSize = sizeof(i16);
+ const int isSigned = 1;
+ int bitstream; // unused
+ const int ret = ov_read(&vf, (char*)buffer+bytesRead, int(size-bytesRead), isBigEndian, wordSize, isSigned, &bitstream);
+ if(ret == 0) { // EOF
+ m_fileEOF = true;
+ return (Status)bytesRead;
+ }
+ else if(ret < 0)
+ WARN_RETURN(LibErrorFromVorbis(ret));
+ else // success
+ {
+ bytesRead += ret;
+ if(bytesRead == size)
+ return (Status)bytesRead;
+ }
+ }
+ }
+
+private:
+ Adapter adapter;
+ OggVorbis_File vf;
+ vorbis_info* info;
+ bool m_fileEOF;
+};
+
+
+//-----------------------------------------------------------------------------
+
+Status OpenOggStream(const OsPath& pathname, OggStreamPtr& stream)
+{
+ PFile file(new File);
+ RETURN_STATUS_IF_ERR(file->Open(pathname, L'r'));
+
+ shared_ptr > tmp(new OggStreamImpl(VorbisFileAdapter(file)));
+ RETURN_STATUS_IF_ERR(tmp->Open());
+ stream = tmp;
+ return INFO::OK;
+}
+
+Status OpenOggNonstream(const PIVFS& vfs, const VfsPath& pathname, OggStreamPtr& stream)
+{
+ shared_ptr contents;
+ size_t size;
+ RETURN_STATUS_IF_ERR(vfs->LoadFile(pathname, contents, size));
+
+ shared_ptr > tmp(new OggStreamImpl(VorbisBufferAdapter(contents, size)));
+ RETURN_STATUS_IF_ERR(tmp->Open());
+ stream = tmp;
+ return INFO::OK;
+}
+
+#endif // CONFIG2_AUDIO
+
diff --git a/source/soundmanager/data/ogg.h b/source/soundmanager/data/ogg.h
new file mode 100644
index 0000000000..c2e2e704d8
--- /dev/null
+++ b/source/soundmanager/data/ogg.h
@@ -0,0 +1,54 @@
+/* Copyright (C) 2012 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. 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.
+ *
+ * 0 A.D. 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 0 A.D. If not, see .
+ */
+#ifndef INCLUDED_OGG
+#define INCLUDED_OGG
+
+#include "lib/config2.h"
+
+#if CONFIG2_AUDIO
+
+#include "lib/external_libraries/openal.h"
+#include "lib/file/vfs/vfs.h"
+
+class OggStream
+{
+public:
+ virtual ~OggStream() { }
+ virtual ALenum Format() = 0;
+ virtual ALsizei SamplingRate() = 0;
+ virtual bool atFileEOF() = 0;
+ virtual Status ResetFile() = 0;
+
+ /**
+ * @return bytes read (<= size) or a (negative) Status
+ **/
+ virtual Status GetNextChunk(u8* buffer, size_t size) = 0;
+};
+
+typedef shared_ptr OggStreamPtr;
+
+extern Status OpenOggStream(const OsPath& pathname, OggStreamPtr& stream);
+
+/**
+ * A non-streaming OggStream (reading the whole file in advance)
+ * that can cope with archived/compressed files.
+ */
+extern Status OpenOggNonstream(const PIVFS& vfs, const VfsPath& pathname, OggStreamPtr& stream);
+
+#endif // CONFIG2_AUDIO
+
+#endif // INCLUDED_OGG