From 9d6d0c6ffb1e98467cea07c48789b35227c8d086 Mon Sep 17 00:00:00 2001
From: elsid <elsid.mail@gmail.com>
Date: Thu, 14 Apr 2022 17:01:36 +0200
Subject: [PATCH 1/3] Move ConstrainedStreamBuf into separate file

---
 components/CMakeLists.txt                     |   2 +-
 components/files/constrainedfilestream.cpp    | 103 +-----------------
 components/files/constrainedfilestream.hpp    |   9 +-
 components/files/constrainedfilestreambuf.cpp |  83 ++++++++++++++
 components/files/constrainedfilestreambuf.hpp |  30 +++++
 5 files changed, 120 insertions(+), 107 deletions(-)
 create mode 100644 components/files/constrainedfilestreambuf.cpp
 create mode 100644 components/files/constrainedfilestreambuf.hpp

diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt
index 57a60322d5..7121bde103 100644
--- a/components/CMakeLists.txt
+++ b/components/CMakeLists.txt
@@ -206,7 +206,7 @@ IF(NOT WIN32 AND NOT APPLE)
 ENDIF()
 add_component_dir (files
     linuxpath androidpath windowspath macospath fixedpath multidircollection collections configurationmanager
-    lowlevelfile constrainedfilestream memorystream hash configfileparser openfile
+    lowlevelfile constrainedfilestream memorystream hash configfileparser openfile constrainedfilestreambuf
     )
 
 add_component_dir (compiler
diff --git a/components/files/constrainedfilestream.cpp b/components/files/constrainedfilestream.cpp
index 0beaf5facc..aafcacba74 100644
--- a/components/files/constrainedfilestream.cpp
+++ b/components/files/constrainedfilestream.cpp
@@ -1,109 +1,8 @@
 #include "constrainedfilestream.hpp"
 
-#include <streambuf>
-#include <algorithm>
-
-#include "lowlevelfile.hpp"
-
-namespace
-{
-// somewhat arbitrary though 64KB buffers didn't seem to improve performance any
-const size_t sBufferSize = 8192;
-}
-
 namespace Files
 {
-    class ConstrainedFileStreamBuf : public std::streambuf
-    {
-
-        size_t mOrigin;
-        size_t mSize;
-
-        LowLevelFile mFile;
-
-        char mBuffer[sBufferSize]{0};
-
-    public:
-        ConstrainedFileStreamBuf(const std::string &fname, size_t start, size_t length)
-        {
-            mFile.open (fname.c_str ());
-            mSize  = length != std::numeric_limits<std::size_t>::max() ? length : mFile.size () - start;
-
-            if (start != 0)
-                mFile.seek(start);
-
-            setg(nullptr,nullptr,nullptr);
-
-            mOrigin = start;
-        }
-
-        int_type underflow() override
-        {
-            if(gptr() == egptr())
-            {
-                size_t toRead = std::min((mOrigin+mSize)-(mFile.tell()), sBufferSize);
-                // Read in the next chunk of data, and set the read pointers on success
-                // Failure will throw exception in LowLevelFile
-                size_t got = mFile.read(mBuffer, toRead);
-                setg(&mBuffer[0], &mBuffer[0], &mBuffer[0]+got);
-            }
-            if(gptr() == egptr())
-                return traits_type::eof();
-
-            return traits_type::to_int_type(*gptr());
-        }
-
-        pos_type seekoff(off_type offset, std::ios_base::seekdir whence, std::ios_base::openmode mode) override
-        {
-            if((mode&std::ios_base::out) || !(mode&std::ios_base::in))
-                return traits_type::eof();
-
-            // new file position, relative to mOrigin
-            size_t newPos;
-            switch (whence)
-            {
-                case std::ios_base::beg:
-                    newPos = offset;
-                    break;
-                case std::ios_base::cur:
-                    newPos = (mFile.tell() - mOrigin - (egptr() - gptr())) + offset;
-                    break;
-                case std::ios_base::end:
-                    newPos = mSize + offset;
-                    break;
-                default:
-                    return traits_type::eof();
-            }
-
-            if (newPos > mSize)
-                return traits_type::eof();
-
-            mFile.seek(mOrigin+newPos);
-
-            // Clear read pointers so underflow() gets called on the next read attempt.
-            setg(nullptr, nullptr, nullptr);
-
-            return newPos;
-        }
-
-        pos_type seekpos(pos_type pos, std::ios_base::openmode mode) override
-        {
-            if((mode&std::ios_base::out) || !(mode&std::ios_base::in))
-                return traits_type::eof();
-
-            if ((size_t)pos > mSize)
-                return traits_type::eof();
-
-            mFile.seek(mOrigin + pos);
-
-            // Clear read pointers so underflow() gets called on the next read attempt.
-            setg(nullptr, nullptr, nullptr);
-            return pos;
-        }
-
-    };
-
-    ConstrainedFileStream::ConstrainedFileStream(std::unique_ptr<std::streambuf> buf)
+    ConstrainedFileStream::ConstrainedFileStream(std::unique_ptr<ConstrainedFileStreamBuf> buf)
         : std::istream(buf.get())
         , mBuf(std::move(buf))
     {
diff --git a/components/files/constrainedfilestream.hpp b/components/files/constrainedfilestream.hpp
index b828f0f6f1..4284705d17 100644
--- a/components/files/constrainedfilestream.hpp
+++ b/components/files/constrainedfilestream.hpp
@@ -1,6 +1,8 @@
 #ifndef OPENMW_CONSTRAINEDFILESTREAM_H
 #define OPENMW_CONSTRAINEDFILESTREAM_H
 
+#include "constrainedfilestreambuf.hpp"
+
 #include <istream>
 #include <memory>
 #include <limits>
@@ -10,14 +12,13 @@ namespace Files
 {
 
 /// A file stream constrained to a specific region in the file, specified by the 'start' and 'length' parameters.
-class ConstrainedFileStream : public std::istream
+class ConstrainedFileStream final : public std::istream
 {
 public:
-    ConstrainedFileStream(std::unique_ptr<std::streambuf> buf);
-    virtual ~ConstrainedFileStream() {};
+    explicit ConstrainedFileStream(std::unique_ptr<ConstrainedFileStreamBuf> buf);
 
 private:
-    std::unique_ptr<std::streambuf> mBuf;
+    std::unique_ptr<ConstrainedFileStreamBuf> mBuf;
 };
 
 typedef std::shared_ptr<std::istream> IStreamPtr;
diff --git a/components/files/constrainedfilestreambuf.cpp b/components/files/constrainedfilestreambuf.cpp
new file mode 100644
index 0000000000..1263523ebe
--- /dev/null
+++ b/components/files/constrainedfilestreambuf.cpp
@@ -0,0 +1,83 @@
+#include "constrainedfilestreambuf.hpp"
+
+#include <algorithm>
+#include <limits>
+
+namespace Files
+{
+    ConstrainedFileStreamBuf::ConstrainedFileStreamBuf(const std::string& fname, std::size_t start, std::size_t length)
+        : mOrigin(start)
+    {
+        mFile.open(fname.c_str());
+        mSize  = length != std::numeric_limits<std::size_t>::max() ? length : mFile.size () - start;
+
+        if (start != 0)
+            mFile.seek(start);
+
+        setg(nullptr, nullptr, nullptr);
+    }
+
+    std::streambuf::int_type ConstrainedFileStreamBuf::underflow()
+    {
+        if (gptr() == egptr())
+        {
+            const std::size_t toRead = std::min((mOrigin + mSize) - (mFile.tell()), sizeof(mBuffer));
+            // Read in the next chunk of data, and set the read pointers on success
+            // Failure will throw exception in LowLevelFile
+            const std::size_t got = mFile.read(mBuffer, toRead);
+            setg(&mBuffer[0], &mBuffer[0], &mBuffer[0] + got);
+        }
+        if (gptr() == egptr())
+            return traits_type::eof();
+
+        return traits_type::to_int_type(*gptr());
+    }
+
+    std::streambuf::pos_type ConstrainedFileStreamBuf::seekoff(off_type offset, std::ios_base::seekdir whence, std::ios_base::openmode mode)
+    {
+        if ((mode & std::ios_base::out) || !(mode & std::ios_base::in))
+            return traits_type::eof();
+
+        // new file position, relative to mOrigin
+        size_t newPos;
+        switch (whence)
+        {
+            case std::ios_base::beg:
+                newPos = offset;
+                break;
+            case std::ios_base::cur:
+                newPos = (mFile.tell() - mOrigin - (egptr() - gptr())) + offset;
+                break;
+            case std::ios_base::end:
+                newPos = mSize + offset;
+                break;
+            default:
+                return traits_type::eof();
+        }
+
+        if (newPos > mSize)
+            return traits_type::eof();
+
+        mFile.seek(mOrigin + newPos);
+
+        // Clear read pointers so underflow() gets called on the next read attempt.
+        setg(nullptr, nullptr, nullptr);
+
+        return newPos;
+    }
+
+    std::streambuf::pos_type ConstrainedFileStreamBuf::seekpos(pos_type pos, std::ios_base::openmode mode)
+    {
+        if ((mode & std::ios_base::out) || !(mode & std::ios_base::in))
+            return traits_type::eof();
+
+        if (static_cast<std::size_t>(pos) > mSize)
+            return traits_type::eof();
+
+        mFile.seek(mOrigin + pos);
+
+        // Clear read pointers so underflow() gets called on the next read attempt.
+        setg(nullptr, nullptr, nullptr);
+        return pos;
+    }
+}
diff --git a/components/files/constrainedfilestreambuf.hpp b/components/files/constrainedfilestreambuf.hpp
new file mode 100644
index 0000000000..46be98c905
--- /dev/null
+++ b/components/files/constrainedfilestreambuf.hpp
@@ -0,0 +1,30 @@
+#ifndef OPENMW_CONSTRAINEDFILESTREAMBUF_H
+#define OPENMW_CONSTRAINEDFILESTREAMBUF_H
+
+#include "lowlevelfile.hpp"
+
+#include <streambuf>
+
+namespace Files
+{
+    /// A file streambuf constrained to a specific region in the file, specified by the 'start' and 'length' parameters.
+    class ConstrainedFileStreamBuf final : public std::streambuf
+    {
+    public:
+        ConstrainedFileStreamBuf(const std::string& fname, std::size_t start, std::size_t length);
+
+        int_type underflow() final;
+
+        pos_type seekoff(off_type offset, std::ios_base::seekdir whence, std::ios_base::openmode mode) final;
+
+        pos_type seekpos(pos_type pos, std::ios_base::openmode mode) final;
+
+    private:
+        std::size_t mOrigin;
+        std::size_t mSize;
+        LowLevelFile mFile;
+        char mBuffer[8192]{0};
+    };
+}
+
+#endif

From c94d8be7bfc0e71f8ee81f8bd4cbf78e00e1bd56 Mon Sep 17 00:00:00 2001
From: elsid <elsid.mail@gmail.com>
Date: Fri, 15 Apr 2022 01:55:14 +0200
Subject: [PATCH 2/3] Add generic StreamWithBuffer owning the underlying buffer

---
 components/files/constrainedfilestream.cpp |  6 ------
 components/files/constrainedfilestream.hpp | 10 ++--------
 components/files/streamwithbuffer.hpp      | 23 ++++++++++++++++++++++
 3 files changed, 25 insertions(+), 14 deletions(-)
 create mode 100644 components/files/streamwithbuffer.hpp

diff --git a/components/files/constrainedfilestream.cpp b/components/files/constrainedfilestream.cpp
index aafcacba74..22a85ce808 100644
--- a/components/files/constrainedfilestream.cpp
+++ b/components/files/constrainedfilestream.cpp
@@ -2,12 +2,6 @@
 
 namespace Files
 {
-    ConstrainedFileStream::ConstrainedFileStream(std::unique_ptr<ConstrainedFileStreamBuf> buf)
-        : std::istream(buf.get())
-        , mBuf(std::move(buf))
-    {
-    }
-
     IStreamPtr openConstrainedFileStream(const std::string& filename, std::size_t start, std::size_t length)
     {
         return std::make_shared<ConstrainedFileStream>(std::make_unique<ConstrainedFileStreamBuf>(filename, start, length));
diff --git a/components/files/constrainedfilestream.hpp b/components/files/constrainedfilestream.hpp
index 4284705d17..3acb04d896 100644
--- a/components/files/constrainedfilestream.hpp
+++ b/components/files/constrainedfilestream.hpp
@@ -2,6 +2,7 @@
 #define OPENMW_CONSTRAINEDFILESTREAM_H
 
 #include "constrainedfilestreambuf.hpp"
+#include "streamwithbuffer.hpp"
 
 #include <istream>
 #include <memory>
@@ -12,14 +13,7 @@ namespace Files
 {
 
 /// A file stream constrained to a specific region in the file, specified by the 'start' and 'length' parameters.
-class ConstrainedFileStream final : public std::istream
-{
-public:
-    explicit ConstrainedFileStream(std::unique_ptr<ConstrainedFileStreamBuf> buf);
-
-private:
-    std::unique_ptr<ConstrainedFileStreamBuf> mBuf;
-};
+using ConstrainedFileStream = StreamWithBuffer<ConstrainedFileStreamBuf>;
 
 typedef std::shared_ptr<std::istream> IStreamPtr;
 
diff --git a/components/files/streamwithbuffer.hpp b/components/files/streamwithbuffer.hpp
new file mode 100644
index 0000000000..dfd1a04376
--- /dev/null
+++ b/components/files/streamwithbuffer.hpp
@@ -0,0 +1,23 @@
+#ifndef OPENMW_COMPONENTS_FILES_STREAMWITHBUFFER_H
+#define OPENMW_COMPONENTS_FILES_STREAMWITHBUFFER_H
+
+#include <istream>
+#include <memory>
+
+namespace Files
+{
+    template <class Buffer>
+    class StreamWithBuffer final : public std::istream
+    {
+        public:
+            explicit StreamWithBuffer(std::unique_ptr<Buffer>&& buffer)
+                : std::istream(buffer.get())
+                , mBuffer(std::move(buffer))
+            {}
+
+        private:
+            std::unique_ptr<Buffer> mBuffer;
+    };
+}
+
+#endif

From 94c1d0cced8fc3a0ca3dd280fa78870fec474e8a Mon Sep 17 00:00:00 2001
From: elsid <elsid.mail@gmail.com>
Date: Fri, 15 Apr 2022 02:15:39 +0200
Subject: [PATCH 3/3] Use unique_ptr to store istream

---
 apps/openmw/mwgui/videowidget.cpp             |  2 +-
 apps/openmw_test_suite/lua/testing_util.hpp   |  2 +-
 components/bsa/compressedbsafile.cpp          |  4 +-
 components/esm/reader.cpp                     | 14 ++---
 components/esm/reader.hpp                     |  2 +-
 components/esm4/reader.cpp                    | 54 +++++++++----------
 components/esm4/reader.hpp                    | 10 ++--
 components/files/constrainedfilestream.cpp    |  2 +-
 components/files/constrainedfilestream.hpp    |  2 +-
 components/fontloader/fontloader.cpp          | 24 ++++-----
 components/nif/niffile.cpp                    |  8 +--
 components/nif/niffile.hpp                    |  4 +-
 components/nif/nifstream.hpp                  |  2 +-
 extern/osg-ffmpeg-videoplayer/videoplayer.cpp |  4 +-
 extern/osg-ffmpeg-videoplayer/videoplayer.hpp |  2 +-
 extern/osg-ffmpeg-videoplayer/videostate.cpp  |  2 +-
 extern/osg-ffmpeg-videoplayer/videostate.hpp  |  4 +-
 17 files changed, 70 insertions(+), 72 deletions(-)

diff --git a/apps/openmw/mwgui/videowidget.cpp b/apps/openmw/mwgui/videowidget.cpp
index 2aea0018d5..36dd7fd3e2 100644
--- a/apps/openmw/mwgui/videowidget.cpp
+++ b/apps/openmw/mwgui/videowidget.cpp
@@ -44,7 +44,7 @@ void VideoWidget::playVideo(const std::string &video)
         return;
     }
 
-    mPlayer->playVideo(videoStream, video);
+    mPlayer->playVideo(std::move(videoStream), video);
 
     osg::ref_ptr<osg::Texture2D> texture = mPlayer->getVideoTexture();
     if (!texture)
diff --git a/apps/openmw_test_suite/lua/testing_util.hpp b/apps/openmw_test_suite/lua/testing_util.hpp
index ba4a418bfb..a40314bd0d 100644
--- a/apps/openmw_test_suite/lua/testing_util.hpp
+++ b/apps/openmw_test_suite/lua/testing_util.hpp
@@ -23,7 +23,7 @@ namespace
 
         Files::IStreamPtr open() override
         {
-            return std::make_shared<std::stringstream>(mContent, std::ios_base::in);
+            return std::make_unique<std::stringstream>(mContent, std::ios_base::in);
         }
 
     private:
diff --git a/components/bsa/compressedbsafile.cpp b/components/bsa/compressedbsafile.cpp
index a3f3c08864..e4117a0b89 100644
--- a/components/bsa/compressedbsafile.cpp
+++ b/components/bsa/compressedbsafile.cpp
@@ -370,7 +370,7 @@ Files::IStreamPtr CompressedBSAFile::getFile(const FileRecord& fileRecord)
         fileStream->read(reinterpret_cast<char*>(&uncompressedSize), sizeof(uint32_t));
         size -= sizeof(uint32_t);
     }
-    std::shared_ptr<Bsa::MemoryInputStream> memoryStreamPtr = std::make_shared<MemoryInputStream>(uncompressedSize);
+    auto memoryStreamPtr = std::make_unique<MemoryInputStream>(uncompressedSize);
 
     if (compressed)
     {
@@ -403,7 +403,7 @@ Files::IStreamPtr CompressedBSAFile::getFile(const FileRecord& fileRecord)
         fileStream->read(memoryStreamPtr->getRawData(), size);
     }
 
-    return std::shared_ptr<std::istream>(memoryStreamPtr, (std::istream*)memoryStreamPtr.get());
+    return std::make_unique<Files::StreamWithBuffer<MemoryInputStream>>(std::move(memoryStreamPtr));
 }
 
 BsaVersion CompressedBSAFile::detectVersion(const std::string& filePath)
diff --git a/components/esm/reader.cpp b/components/esm/reader.cpp
index d3732abb1e..519c547bb8 100644
--- a/components/esm/reader.cpp
+++ b/components/esm/reader.cpp
@@ -26,7 +26,7 @@ namespace ESM
 
             if (modVer == ESM4::REC_TES4)
             {
-                return new ESM4::Reader(esmStream, filename);
+                return new ESM4::Reader(std::move(esmStream), filename);
             }
             else
             {
@@ -38,15 +38,15 @@ namespace ESM
     }
 
     bool Reader::getStringImpl(std::string& str, std::size_t size,
-            Files::IStreamPtr filestream, ToUTF8::StatelessUtf8Encoder* encoder, bool hasNull)
+            std::istream& stream, ToUTF8::StatelessUtf8Encoder* encoder, bool hasNull)
     {
         std::size_t newSize = size;
 
         if (encoder)
         {
             std::string input(size, '\0');
-            filestream->read(input.data(), size);
-            if (filestream->gcount() == static_cast<std::streamsize>(size))
+            stream.read(input.data(), size);
+            if (stream.gcount() == static_cast<std::streamsize>(size))
             {
                 encoder->getUtf8(input, ToUTF8::BufferAllocationPolicy::FitToRequiredSize, str);
                 return true;
@@ -58,13 +58,13 @@ namespace ESM
                 newSize -= 1; // don't read the null terminator yet
 
             str.resize(newSize); // assumed C++11
-            filestream->read(&str[0], newSize);
-            if ((std::size_t)filestream->gcount() == newSize)
+            stream.read(&str[0], newSize);
+            if (static_cast<std::size_t>(stream.gcount()) == newSize)
             {
                 if (hasNull)
                 {
                     char ch;
-                    filestream->read(&ch, 1); // read the null terminator
+                    stream.read(&ch, 1); // read the null terminator
                     assert (ch == '\0'
                             && "ESM4::Reader::getString string is not terminated with a null");
                 }
diff --git a/components/esm/reader.hpp b/components/esm/reader.hpp
index 5356d9bd01..ffc416dc18 100644
--- a/components/esm/reader.hpp
+++ b/components/esm/reader.hpp
@@ -53,7 +53,7 @@ namespace ESM
 
     protected:
         bool getStringImpl(std::string& str, std::size_t size,
-                Files::IStreamPtr filestream, ToUTF8::StatelessUtf8Encoder* encoder, bool hasNull = false);
+                std::istream& stream, ToUTF8::StatelessUtf8Encoder* encoder, bool hasNull = false);
     };
 }
 
diff --git a/components/esm4/reader.cpp b/components/esm4/reader.cpp
index 1a8fff789a..ce7ede2bf4 100644
--- a/components/esm4/reader.cpp
+++ b/components/esm4/reader.cpp
@@ -64,8 +64,8 @@ ReaderContext::ReaderContext() : modIndex(0), recHeaderSize(sizeof(RecordHeader)
     currCellGrid.grid.y = 0;
 }
 
-Reader::Reader(Files::IStreamPtr esmStream, const std::string& filename)
-    : mEncoder(nullptr), mFileSize(0), mStream(esmStream)
+Reader::Reader(Files::IStreamPtr&& esmStream, const std::string& filename)
+    : mEncoder(nullptr), mFileSize(0), mStream(std::move(esmStream))
 {
     // used by ESMReader only?
     mCtx.filename = filename;
@@ -130,8 +130,7 @@ bool Reader::restoreContext(const ReaderContext& ctx)
 {
     if (mSavedStream) // TODO: doesn't seem to ever happen
     {
-        mStream = mSavedStream;
-        mSavedStream.reset();
+        mStream = std::move(mSavedStream);
     }
 
     mCtx.groupStack.clear(); // probably not necessary since it will be overwritten
@@ -148,11 +147,11 @@ void Reader::close()
     //mHeader.blank();
 }
 
-void Reader::openRaw(Files::IStreamPtr esmStream, const std::string& filename)
+void Reader::openRaw(Files::IStreamPtr&& stream, const std::string& filename)
 {
     close();
 
-    mStream = esmStream;
+    mStream = std::move(stream);
     mCtx.filename = filename;
     mCtx.fileRead = 0;
     mStream->seekg(0, mStream->end);
@@ -161,9 +160,9 @@ void Reader::openRaw(Files::IStreamPtr esmStream, const std::string& filename)
 
 }
 
-void Reader::open(Files::IStreamPtr esmStream, const std::string &filename)
+void Reader::open(Files::IStreamPtr&& stream, const std::string &filename)
 {
-    openRaw(esmStream, filename);
+    openRaw(std::move(stream), filename);
 
     // should at least have the size of ESM3 record header (20 or 24 bytes for ESM4)
     assert (mFileSize >= 16);
@@ -210,32 +209,33 @@ void Reader::buildLStringIndex(const std::string& stringFile, LocalizedStringTyp
     sp.type = stringType;
 
     // TODO: possibly check if the resource exists?
-    Files::IStreamPtr filestream = Files::IStreamPtr(Files::openConstrainedFileStream(stringFile));
+    Files::IStreamPtr filestream = Files::openConstrainedFileStream(stringFile);
 
     filestream->seekg(0, std::ios::end);
     std::size_t fileSize = filestream->tellg();
     filestream->seekg(0, std::ios::beg);
 
+    std::istream* stream = filestream.get();
     switch (stringType)
     {
-        case Type_Strings:   mStrings =   filestream; break;
-        case Type_ILStrings: mILStrings = filestream; break;
-        case Type_DLStrings: mDLStrings = filestream; break;
+        case Type_Strings:   mStrings =   std::move(filestream); break;
+        case Type_ILStrings: mILStrings = std::move(filestream); break;
+        case Type_DLStrings: mDLStrings = std::move(filestream); break;
         default:
             throw std::runtime_error("ESM4::Reader::unknown localised string type");
     }
 
-    filestream->read((char*)&numEntries, sizeof(numEntries));
-    filestream->read((char*)&dataSize, sizeof(dataSize));
+    stream->read((char*)&numEntries, sizeof(numEntries));
+    stream->read((char*)&dataSize, sizeof(dataSize));
     std::size_t dataStart = fileSize - dataSize;
     for (unsigned int i = 0; i < numEntries; ++i)
     {
-        filestream->read((char*)&stringId, sizeof(stringId));
-        filestream->read((char*)&sp.offset, sizeof(sp.offset));
+        stream->read((char*)&stringId, sizeof(stringId));
+        stream->read((char*)&sp.offset, sizeof(sp.offset));
         sp.offset += (std::uint32_t)dataStart;
         mLStringIndex[stringId] = sp;
     }
-    //assert (dataStart - filestream->tell() == 0 && "String file start of data section mismatch");
+    //assert (dataStart - stream->tell() == 0 && "String file start of data section mismatch");
 }
 
 void Reader::getLocalizedString(std::string& str)
@@ -256,13 +256,13 @@ void Reader::getLocalizedStringImpl(const FormId stringId, std::string& str)
 
     if (it != mLStringIndex.end())
     {
-        Files::IStreamPtr filestream;
+        std::istream* filestream = nullptr;
 
         switch (it->second.type)
         {
             case Type_Strings: // no string size provided
             {
-                filestream = mStrings;
+                filestream = mStrings.get();
                 filestream->seekg(it->second.offset);
 
                 char ch;
@@ -275,8 +275,8 @@ void Reader::getLocalizedStringImpl(const FormId stringId, std::string& str)
                 str = std::string(data.data());
                 return;
             }
-            case Type_ILStrings: filestream = mILStrings; break;
-            case Type_DLStrings: filestream = mDLStrings; break;
+            case Type_ILStrings: filestream = mILStrings.get(); break;
+            case Type_DLStrings: filestream = mDLStrings.get(); break;
             default:
                 throw std::runtime_error("ESM4::Reader::getLocalizedString unknown string type");
         }
@@ -285,7 +285,7 @@ void Reader::getLocalizedStringImpl(const FormId stringId, std::string& str)
         filestream->seekg(it->second.offset);
         std::uint32_t size = 0;
         filestream->read((char*)&size, sizeof(size));
-        getStringImpl(str, size, filestream, mEncoder, true); // expect null terminated string
+        getStringImpl(str, size, *filestream, mEncoder, true); // expect null terminated string
     }
     else
         throw std::runtime_error("ESM4::Reader::getLocalizedString localized string not found");
@@ -296,8 +296,7 @@ bool Reader::getRecordHeader()
     // FIXME: this seems very hacky but we may have skipped subrecords from within an inflated data block
     if (/*mStream->eof() && */mSavedStream)
     {
-        mStream = mSavedStream;
-        mSavedStream.reset();
+        mStream = std::move(mSavedStream);
     }
 
     mStream->read((char*)&mCtx.recordHeader, mCtx.recHeaderSize);
@@ -336,12 +335,11 @@ void Reader::getRecordData(bool dump)
         Bsa::MemoryInputStream compressedRecord(recordSize);
         mStream->read(compressedRecord.getRawData(), recordSize);
         std::istream *fileStream = (std::istream*)&compressedRecord;
-        mSavedStream = mStream;
+        mSavedStream = std::move(mStream);
 
         mCtx.recordHeader.record.dataSize = uncompressedSize - sizeof(uncompressedSize);
 
-        std::shared_ptr<Bsa::MemoryInputStream> memoryStreamPtr
-            = std::make_shared<Bsa::MemoryInputStream>(uncompressedSize);
+        auto memoryStreamPtr = std::make_unique<Bsa::MemoryInputStream>(uncompressedSize);
 
         boost::iostreams::filtering_streambuf<boost::iostreams::input> inputStreamBuf;
         inputStreamBuf.push(boost::iostreams::zlib_decompressor());
@@ -370,7 +368,7 @@ if (dump)
         std::cout << ss.str() << std::endl;
 }
 //#endif
-        mStream = std::shared_ptr<std::istream>(memoryStreamPtr, (std::istream*)memoryStreamPtr.get());
+        mStream = std::make_unique<Files::StreamWithBuffer<Bsa::MemoryInputStream>>(std::move(memoryStreamPtr));
     }
 }
 
diff --git a/components/esm4/reader.hpp b/components/esm4/reader.hpp
index 60676b116b..68d5b9eb04 100644
--- a/components/esm4/reader.hpp
+++ b/components/esm4/reader.hpp
@@ -114,17 +114,17 @@ namespace ESM4 {
         //void close();
 
         // Raw opening. Opens the file and sets everything up but doesn't parse the header.
-        void openRaw(Files::IStreamPtr esmStream, const std::string& filename);
+        void openRaw(Files::IStreamPtr&& stream, const std::string& filename);
 
         // Load ES file from a new stream, parses the header.
         // Closes the currently open file first, if any.
-        void open(Files::IStreamPtr esmStream, const std::string& filename);
+        void open(Files::IStreamPtr&& stream, const std::string& filename);
 
         Reader() = default;
 
     public:
 
-        Reader(Files::IStreamPtr esmStream, const std::string& filename);
+        Reader(Files::IStreamPtr&& esmStream, const std::string& filename);
         ~Reader();
 
         // FIXME: should be private but ESMTool uses it
@@ -280,10 +280,10 @@ namespace ESM4 {
 
         // Note: uses the string size from the subrecord header rather than checking null termination
         bool getZString(std::string& str) {
-            return getStringImpl(str, mCtx.subRecordHeader.dataSize, mStream, mEncoder, true);
+            return getStringImpl(str, mCtx.subRecordHeader.dataSize, *mStream, mEncoder, true);
         }
         bool getString(std::string& str) {
-            return getStringImpl(str, mCtx.subRecordHeader.dataSize, mStream, mEncoder);
+            return getStringImpl(str, mCtx.subRecordHeader.dataSize, *mStream, mEncoder);
         }
 
         void enterGroup();
diff --git a/components/files/constrainedfilestream.cpp b/components/files/constrainedfilestream.cpp
index 22a85ce808..a56ae01aa0 100644
--- a/components/files/constrainedfilestream.cpp
+++ b/components/files/constrainedfilestream.cpp
@@ -4,6 +4,6 @@ namespace Files
 {
     IStreamPtr openConstrainedFileStream(const std::string& filename, std::size_t start, std::size_t length)
     {
-        return std::make_shared<ConstrainedFileStream>(std::make_unique<ConstrainedFileStreamBuf>(filename, start, length));
+        return std::make_unique<ConstrainedFileStream>(std::make_unique<ConstrainedFileStreamBuf>(filename, start, length));
     }
 }
diff --git a/components/files/constrainedfilestream.hpp b/components/files/constrainedfilestream.hpp
index 3acb04d896..bed4242ec4 100644
--- a/components/files/constrainedfilestream.hpp
+++ b/components/files/constrainedfilestream.hpp
@@ -15,7 +15,7 @@ namespace Files
 /// A file stream constrained to a specific region in the file, specified by the 'start' and 'length' parameters.
 using ConstrainedFileStream = StreamWithBuffer<ConstrainedFileStreamBuf>;
 
-typedef std::shared_ptr<std::istream> IStreamPtr;
+typedef std::unique_ptr<std::istream> IStreamPtr;
 
 IStreamPtr openConstrainedFileStream(const std::string& filename, std::size_t start = 0,
     std::size_t length = std::numeric_limits<std::size_t>::max());
diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp
index d0125398f1..b67283d01f 100644
--- a/components/fontloader/fontloader.cpp
+++ b/components/fontloader/fontloader.cpp
@@ -137,12 +137,12 @@ namespace
         }
     }
 
-    [[noreturn]] void fail (Files::IStreamPtr file, const std::string& fileName, const std::string& message)
+    [[noreturn]] void fail(std::istream& stream, const std::string& fileName, const std::string& message)
     {
         std::stringstream error;
         error << "Font loading error: " << message;
         error << "\n  File: " << fileName;
-        error << "\n  Offset: 0x" << std::hex << file->tellg();
+        error << "\n  Offset: 0x" << std::hex << stream.tellg();
         throw std::runtime_error(error.str());
     }
 
@@ -252,33 +252,33 @@ namespace Gui
         float fontSize;
         file->read((char*)&fontSize, sizeof(fontSize));
         if (!file->good())
-            fail(file, fileName, "File too small to be a valid font");
+            fail(*file, fileName, "File too small to be a valid font");
 
         int one;
         file->read((char*)&one, sizeof(one));
         if (!file->good())
-            fail(file, fileName, "File too small to be a valid font");
+            fail(*file, fileName, "File too small to be a valid font");
 
         if (one != 1)
-            fail(file, fileName, "Unexpected value");
+            fail(*file, fileName, "Unexpected value");
 
         file->read((char*)&one, sizeof(one));
         if (!file->good())
-            fail(file, fileName, "File too small to be a valid font");
+            fail(*file, fileName, "File too small to be a valid font");
 
         if (one != 1)
-            fail(file, fileName, "Unexpected value");
+            fail(*file, fileName, "Unexpected value");
 
         char name_[284];
         file->read(name_, sizeof(name_));
         if (!file->good())
-            fail(file, fileName, "File too small to be a valid font");
+            fail(*file, fileName, "File too small to be a valid font");
         std::string name(name_);
 
         GlyphInfo data[256];
         file->read((char*)data, sizeof(data));
         if (!file->good())
-            fail(file, fileName, "File too small to be a valid font");
+            fail(*file, fileName, "File too small to be a valid font");
 
         file.reset();
 
@@ -292,16 +292,16 @@ namespace Gui
         bitmapFile->read((char*)&height, sizeof(int));
 
         if (!bitmapFile->good())
-            fail(bitmapFile, bitmapFilename, "File too small to be a valid bitmap");
+            fail(*bitmapFile, bitmapFilename, "File too small to be a valid bitmap");
 
         if (width <= 0 || height <= 0)
-            fail(bitmapFile, bitmapFilename, "Width and height must be positive");
+            fail(*bitmapFile, bitmapFilename, "Width and height must be positive");
 
         std::vector<char> textureData;
         textureData.resize(width*height*4);
         bitmapFile->read(&textureData[0], width*height*4);
         if (!bitmapFile->good())
-            fail(bitmapFile, bitmapFilename, "File too small to be a valid bitmap");
+            fail(*bitmapFile, bitmapFilename, "File too small to be a valid bitmap");
         bitmapFile.reset();
 
         std::string resourceName;
diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp
index 57d1ce6457..4e0fd5a614 100644
--- a/components/nif/niffile.cpp
+++ b/components/nif/niffile.cpp
@@ -12,10 +12,10 @@ namespace Nif
 {
 
 /// Open a NIF stream. The name is used for error messages.
-NIFFile::NIFFile(Files::IStreamPtr stream, const std::string &name)
+NIFFile::NIFFile(Files::IStreamPtr&& stream, const std::string &name)
     : filename(name)
 {
-    parse(stream);
+    parse(std::move(stream));
 }
 
 template <typename NodeType, RecordType recordType>
@@ -170,12 +170,12 @@ std::string NIFFile::printVersion(unsigned int version)
     return stream.str();
 }
 
-void NIFFile::parse(Files::IStreamPtr stream)
+void NIFFile::parse(Files::IStreamPtr&& stream)
 {
     const std::array<std::uint64_t, 2> fileHash = Files::getHash(filename, *stream);
     hash.append(reinterpret_cast<const char*>(fileHash.data()), fileHash.size() * sizeof(std::uint64_t));
 
-    NIFStream nif (this, stream);
+    NIFStream nif (this, std::move(stream));
 
     // Check the header string
     std::string head = nif.getVersionString();
diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp
index def2b8870d..aa53f516fd 100644
--- a/components/nif/niffile.hpp
+++ b/components/nif/niffile.hpp
@@ -69,7 +69,7 @@ class NIFFile final : public File
     static std::atomic_bool sLoadUnsupportedFiles;
 
     /// Parse the file
-    void parse(Files::IStreamPtr stream);
+    void parse(Files::IStreamPtr&& stream);
 
     /// Get the file's version in a human readable form
     ///\returns A string containing a human readable NIF version number
@@ -107,7 +107,7 @@ public:
     }
 
     /// Open a NIF stream. The name is used for error messages.
-    NIFFile(Files::IStreamPtr stream, const std::string &name);
+    NIFFile(Files::IStreamPtr&& stream, const std::string &name);
 
     /// Get a given record
     Record *getRecord(size_t index) const override
diff --git a/components/nif/nifstream.hpp b/components/nif/nifstream.hpp
index 04243c2506..04583131b9 100644
--- a/components/nif/nifstream.hpp
+++ b/components/nif/nifstream.hpp
@@ -62,7 +62,7 @@ public:
 
     NIFFile * const file;
 
-    NIFStream (NIFFile * file, Files::IStreamPtr inp): inp (inp), file (file) {}
+    NIFStream (NIFFile * file, Files::IStreamPtr&& inp): inp (std::move(inp)), file (file) {}
 
     void skip(size_t size) { inp->ignore(size); }
 
diff --git a/extern/osg-ffmpeg-videoplayer/videoplayer.cpp b/extern/osg-ffmpeg-videoplayer/videoplayer.cpp
index 28ac3b36c2..e101bd4870 100644
--- a/extern/osg-ffmpeg-videoplayer/videoplayer.cpp
+++ b/extern/osg-ffmpeg-videoplayer/videoplayer.cpp
@@ -27,7 +27,7 @@ void VideoPlayer::setAudioFactory(MovieAudioFactory *factory)
     mAudioFactory.reset(factory);
 }
 
-void VideoPlayer::playVideo(std::shared_ptr<std::istream> inputstream, const std::string& name)
+void VideoPlayer::playVideo(std::unique_ptr<std::istream>&& inputstream, const std::string& name)
 {
     if(mState)
         close();
@@ -35,7 +35,7 @@ void VideoPlayer::playVideo(std::shared_ptr<std::istream> inputstream, const std
     try {
         mState = new VideoState;
         mState->setAudioFactory(mAudioFactory.get());
-        mState->init(inputstream, name);
+        mState->init(std::move(inputstream), name);
 
         // wait until we have the first picture
         while (mState->video_st && !mState->mTexture.get())
diff --git a/extern/osg-ffmpeg-videoplayer/videoplayer.hpp b/extern/osg-ffmpeg-videoplayer/videoplayer.hpp
index 97ef13032d..801f9ab560 100644
--- a/extern/osg-ffmpeg-videoplayer/videoplayer.hpp
+++ b/extern/osg-ffmpeg-videoplayer/videoplayer.hpp
@@ -42,7 +42,7 @@ namespace Video
         /// Play the given video. If a video is already playing, the old video is closed first.
         /// @note The video will be unpaused by default. Use the pause() and play() methods to control pausing.
         /// @param name A name for the video stream - only used for logging purposes.
-        void playVideo (std::shared_ptr<std::istream> inputstream, const std::string& name);
+        void playVideo(std::unique_ptr<std::istream>&& inputstream, const std::string& name);
 
         /// Get the current playback time position in the video, in seconds
         double getCurrentTime();
diff --git a/extern/osg-ffmpeg-videoplayer/videostate.cpp b/extern/osg-ffmpeg-videoplayer/videostate.cpp
index 9b07ece14d..096651dfd8 100644
--- a/extern/osg-ffmpeg-videoplayer/videostate.cpp
+++ b/extern/osg-ffmpeg-videoplayer/videostate.cpp
@@ -714,7 +714,7 @@ int VideoState::stream_open(int stream_index, AVFormatContext *pFormatCtx)
     return 0;
 }
 
-void VideoState::init(std::shared_ptr<std::istream> inputstream, const std::string &name)
+void VideoState::init(std::unique_ptr<std::istream>&& inputstream, const std::string &name)
 {
     int video_index = -1;
     int audio_index = -1;
diff --git a/extern/osg-ffmpeg-videoplayer/videostate.hpp b/extern/osg-ffmpeg-videoplayer/videostate.hpp
index 8fdbbe64bf..3681a64976 100644
--- a/extern/osg-ffmpeg-videoplayer/videostate.hpp
+++ b/extern/osg-ffmpeg-videoplayer/videostate.hpp
@@ -128,7 +128,7 @@ struct VideoState {
 
     void setAudioFactory(MovieAudioFactory* factory);
 
-    void init(std::shared_ptr<std::istream> inputstream, const std::string& name);
+    void init(std::unique_ptr<std::istream>&& inputstream, const std::string& name);
     void deinit();
 
     void setPaused(bool isPaused);
@@ -165,7 +165,7 @@ struct VideoState {
 
     ExternalClock mExternalClock;
 
-    std::shared_ptr<std::istream> stream;
+    std::unique_ptr<std::istream> stream;
     AVFormatContext* format_ctx;
     AVCodecContext* video_ctx;
     AVCodecContext* audio_ctx;