You can safely ignore COM at this point. It's pretty much dead.
Though in answer to your comment. IUnknown represents the methods you can call on any arbitrary unknown COM object. Given an unknown object, it allows you to get access to the interfaces it supports, and manage the object's life cycle.
For implementing a memory stream buffer, you can memcpy from the internal stream buffer to the one passed as a parameter to read, and then update the tracking info for the internal stream position. Remember to bounds check things first.
Also, keep in mind I left out all error checking in my examples. It might make sense to return the number of bytes actually transferred; a very common and generic API design that pushes error checking onto the client. Or you might thrown an exception if not enough bytes are available to do the full requested transfer; a very easy and safe to use API design. You can of course implement one in terms of the other. Though that might not be ideal if you want to make certain guarantees about stream state in the event of an error. For instance, does a partial read that raises an exception update the internal stream state, or leave it where it started?
I see you've found a use where you need to seek within your stream. Might I suggest an additional interface layer for the seek capability? Consider the following inheritance possibility:
StreamReader : SeekableStreamReader : FileStreamReader
StreamReader : SeekableStreamReader : MemoryStreamReader
StreamReader : SocketStreamReader
StreamReader : ZipStreamReader
Not all streams support seeking in a clean way. It would be a cleaner design to not include seeking in the base interface.
Hmm. I had some misgivings about the design of the two constructor option I suggested before. Now that I'm looking at it, I like it even less. My original issue was it seemed like it would be redundant code that was better written elsewhere. My second concern now is about memory safety. It's not immediately obvious looking at this that it's safe to do. It does work here, because the entire stream is read in the constructor, but is perhaps a poor pattern that could get copied elsewhere that isn't so suitable. Consider memory management for a moment. When and how is the FileStreamReader class cleaned up?
MapData(string filename, bool saveGame = false) : MapData(FileStreamReader(filename), saveGame) {}
Here the stream is a temporary object that gets destroyed at the end of the second constructor. This is fine, since the entire stream will be read in the nested call to the first constructor, so the stream isn't needed anymore by the end of the second constructor.
If we consider a different object, like say VOL files, where a passed in stream might be stored, and later used to read further chunks of data, this design fails in unexpected ways. It fails because the temporary stream object would be destroyed at the end of the outer constructor call. The stored pointer is now a dangling pointer. Potentially future calls will still work using the ghost of the destroyed stream object. Until one day it suddenly doesn't, and everything blows up because that memory of the deleted stream object got reused.
Another option is to new the stream object using dynamically allocated memory. Slightly less efficient if you don't need dynamic memory, but the real issue is ownership. Who cleans up the stream object?
Assume the object doesn't own the stream, it's just temporarily using the stream object. A constructor can't return a value. At the end of the constructor, the stream pointer silently goes out of scope, and is lost to the outside program. As the object doesn't own the stream, it's not going to try and clean it up. It now becomes a memory leak, and the program leaves files open after it's done using them. Not good.
Now assume the object does own the passed stream. Ok, now things work, and when the object is done with the stream, it can delete it, closing files and freeing memory. That works great so long as you only ever pass streams that will be used and discarded. You can no longer pass in a re-usable stream. Consider processing the contents of a Zip file, create objects as you unpack each internal file. If the objects claimed ownership of the stream and closed/deleted the stream after they were done reading their section, there would be no way to continue reading and loading the rest of the objects. It also doesn't work for streams stored in local variables on the stack, since you can't delete those. This is very restrictive.
Again though, this isn't an issue here because your map object reads the whole stream in the constructor, and doesn't store a pointer to the stream internally. This let's the point of call deal with cleaning up the stream. That might be letting locally declared streams on the stack go out of scope and get cleaned up automatically, such as your outer constructor, or it might mean dynamically allocated streams get deleted after use in some calling function. The pattern is fine, but it's not clear that it's safe unless you know the stream pointer isn't stored and used later. An assumption that should be documented somewhere.
In terms of ease of use and being generic, I think it's best to have a utility function that finds resources, either loosely in a folder, or packed in a VOL file, and returns a stream. A loading function (possibly a second constructor) can call the utility function to get an appropriate stream, whatever the source, pass that stream in to the Map constructor, and then later discard the stream. The utility function can then be reused for loading any other type of data.
It also seems likely that utility function will be some sort of class member function, such as:
StreamReader& ResourceManager.Open(string resourceName);
The reason for making it a member function, is that it must search some list of VOL files, which implies some kind of internal state. The resource manager needs to know what VOL files, and for efficiency probably wants to keep them open, with the headers and index already parsed, but probably not do a full load of the contents.
The size variable should probably be a larger type, such as size_t. Since Read copies data into memory, the read size is limited by the memory size. This makes size_t appropriate, which is defined as being large enough to span the memory address space. For 32-bit that means 4GB max, for 64-bit that's much much larger. Regardless of platform size, files greater than 4GB will be supported, so file/stream size and seek positions should always allow at least 64 bits. I believe fstream uses a "pos_type" typedef for an appropriately sized type.