diff --git a/common/concatstream.cpp b/common/concatstream.cpp
new file mode 100644
index 00000000000..33d1153c917
--- /dev/null
+++ b/common/concatstream.cpp
@@ -0,0 +1,140 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * 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 3 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, see .
+ *
+ */
+
+#include "common/concatstream.h"
+
+namespace Common {
+
+ConcatReadStream::ConcatReadStream(ParentStreamArray parentStreams) : _parentStreams(parentStreams), _totalSize(0), _linearPos(0), _volume(0), _volumePos(0), _err(false), _eos(false) {
+ _sizes.resize(parentStreams.size());
+ _startOffsets.resize(parentStreams.size());
+ for (uint i = 0; i < parentStreams.size(); i++) {
+ _sizes[i] = parentStreams[i]->size();
+ _totalSize += _sizes[i];
+ }
+ _startOffsets[0] = 0;
+ for (uint i = 1; i < parentStreams.size(); i++)
+ _startOffsets[i] = _startOffsets[i - 1] + _sizes[i - 1];
+}
+
+bool ConcatReadStream::seek(int64 offset, int whence) {
+ int64 target = 0;
+ switch (whence) {
+ case SEEK_SET:
+ target = offset;
+ break;
+ case SEEK_CUR:
+ target = _linearPos + offset;
+ break;
+ case SEEK_END:
+ target = _totalSize + offset;
+ break;
+ default:
+ return false;
+ }
+
+ if (target < 0 || target > _totalSize) {
+ return false;
+ }
+
+ _linearPos = target;
+ _eos = false;
+ _err = false;
+
+ // Special case: seek'ing to the EOF.
+ if (target == _totalSize) {
+ if (_parentStreams.empty()) {
+ _volume = 0;
+ _volumePos = 0;
+ return true;
+ }
+ _volume = _parentStreams.size() - 1;
+ _volumePos = _sizes[_parentStreams.size() - 1];
+ return true;
+ }
+
+ // Find volume
+ for (unsigned vol = 0; vol < _parentStreams.size(); vol++) {
+ if (_startOffsets[vol] <= _linearPos &&
+ _linearPos < _startOffsets[vol] + _sizes[vol]) {
+ _volume = vol;
+ _volumePos = _linearPos - _startOffsets[vol];
+ return true;
+ }
+ }
+
+ // Should never be reached.
+ _err = true;
+ return false;
+}
+
+bool ConcatReadStream::seekToVolume(int volume, int64 offset) {
+ if (volume < 0 || (uint) volume >= _parentStreams.size()
+ || offset < 0 || offset >= _sizes[volume]) {
+ _err = true;
+ return false;
+ }
+
+ _err = false;
+ _eos = false;
+ _volume = volume;
+ _volumePos = offset;
+ _linearPos = _startOffsets[volume] + offset;
+ return true;
+}
+
+uint32 ConcatReadStream::read(void *dataPtr, uint32 dataSize) {
+ uint32 rem = dataSize;
+ uint32 alreadyRead = 0;
+ byte *curPtr = (byte*) dataPtr;
+ while (rem > 0) {
+ int64 avail = _startOffsets[_volume] + _sizes[_volume];
+ while (avail == 0) {
+ if (_volume + 1 >= _startOffsets.size()) {
+ _eos = true;
+ return alreadyRead;
+ }
+ _volume++;
+ _volumePos = 0;
+ avail = _startOffsets[_volume] + _sizes[_volume];
+ }
+
+ uint32 toRead = MIN((int64) rem, avail);
+ _parentStreams[_volume]->seek(_volumePos); // Also clears error and EOS.
+ uint32 actuallyRead = _parentStreams[_volume]->read(curPtr, toRead);
+ alreadyRead += actuallyRead;
+ rem -= actuallyRead;
+ curPtr += actuallyRead;
+ _volumePos += actuallyRead;
+ _linearPos += actuallyRead;
+ if (_volumePos == _sizes[_volume] && _volume + 1 < _startOffsets.size()) {
+ _volume++;
+ _volumePos = 0;
+ }
+ if (_parentStreams[_volume]->err()) {
+ _err = true;
+ return alreadyRead;
+ }
+ }
+
+ return alreadyRead;
+}
+} // End of namespace Common
diff --git a/common/concatstream.h b/common/concatstream.h
new file mode 100644
index 00000000000..0fb55677b39
--- /dev/null
+++ b/common/concatstream.h
@@ -0,0 +1,67 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * 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 3 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, see .
+ *
+ */
+
+#ifndef COMMON_CONCATSTREAM_H
+#define COMMON_CONCATSTREAM_H
+
+#include "common/array.h"
+#include "common/ptr.h"
+#include "common/stream.h"
+
+namespace Common {
+/*
+ * ConcatReadStream provides access to a virtually concatenated stream.
+ *
+ * Manipulating the parent stream directly /will/ mess up a concatstream.
+ *
+ * Assumptions:
+ * - number of streams is small so iterating through array sized by N is cheap
+ * - size of streams doesn't change
+ */
+class ConcatReadStream : public SeekableReadStream {
+private:
+ typedef Common::Array> ParentStreamArray;
+ ParentStreamArray _parentStreams;
+ Common::Array _sizes;
+ Common::Array _startOffsets;
+
+ uint32 _totalSize, _linearPos;
+ uint32 _volume, _volumePos;
+ bool _err, _eos;
+public:
+ ConcatReadStream(ParentStreamArray parentStreams);
+
+ int64 pos() const override { return _linearPos; }
+ int64 size() const override { return _totalSize; }
+ bool eos() const override { return _eos; }
+ bool err() const override { return _err; }
+ void clearErr() override {
+ _err = false;
+ _eos = false;
+ }
+ bool seek(int64 offset, int whence = SEEK_SET) override;
+ uint32 read(void *dataPtr, uint32 dataSize) override;
+ bool seekToVolume(int volume, int64 offset);
+};
+
+}
+
+#endif // COMMON_CONCATSTREAM_H
diff --git a/common/module.mk b/common/module.mk
index 42720db2a0d..ee6a7d4cad5 100644
--- a/common/module.mk
+++ b/common/module.mk
@@ -5,6 +5,7 @@ MODULE_OBJS := \
archive.o \
base-str.o \
clickteam.o \
+ concatstream.o \
config-manager.o \
coroutines.o \
dcl.o \