Index: build/autotools/autoconfiscate.sh
===================================================================
--- build/autotools/autoconfiscate.sh	(revision 17577)
+++ build/autotools/autoconfiscate.sh	(working copy)
@@ -58,6 +58,7 @@
 svn export ./src/mpt/format         bin/dist-autotools/src/mpt/format
 #svn export ./src/mpt/fs             bin/dist-autotools/src/mpt/fs
 svn export ./src/mpt/io             bin/dist-autotools/src/mpt/io
+svn export ./src/mpt/io_file        bin/dist-autotools/src/mpt/io_file
 svn export ./src/mpt/io_read        bin/dist-autotools/src/mpt/io_read
 svn export ./src/mpt/io_write       bin/dist-autotools/src/mpt/io_write
 #svn export ./src/mpt/json           bin/dist-autotools/src/mpt/json
@@ -125,6 +126,7 @@
 cp -r ./src/mpt/format         bin/dist-autotools/src/mpt/format
 #cp -r ./src/mpt/fs             bin/dist-autotools/src/mpt/fs
 cp -r ./src/mpt/io             bin/dist-autotools/src/mpt/io
+cp -r ./src/mpt/io_file        bin/dist-autotools/src/mpt/io_file
 cp -r ./src/mpt/io_read        bin/dist-autotools/src/mpt/io_read
 cp -r ./src/mpt/io_write       bin/dist-autotools/src/mpt/io_write
 #cp -r ./src/mpt/json           bin/dist-autotools/src/mpt/json
Index: build/autotools/Makefile.am
===================================================================
--- build/autotools/Makefile.am	(revision 17577)
+++ build/autotools/Makefile.am	(working copy)
@@ -190,6 +190,14 @@
 MPT_FILES_SRC_MPT += src/mpt/io/io_span.hpp
 MPT_FILES_SRC_MPT += src/mpt/io/io_stdstream.hpp
 MPT_FILES_SRC_MPT += src/mpt/io/io_virtual_wrapper.hpp
+MPT_FILES_SRC_MPT += src/mpt/io_file/fileadapter.hpp
+MPT_FILES_SRC_MPT += src/mpt/io_file/fileref.hpp
+MPT_FILES_SRC_MPT += src/mpt/io_file/fstream.hpp
+MPT_FILES_SRC_MPT += src/mpt/io_file/inputfile.hpp
+MPT_FILES_SRC_MPT += src/mpt/io_file/inputfile_filecursor.hpp
+MPT_FILES_SRC_MPT += src/mpt/io_file/outputfile.hpp
+MPT_FILES_SRC_MPT += src/mpt/io_file/unique_basename.hpp
+MPT_FILES_SRC_MPT += src/mpt/io_file/unique_tempfilename.hpp
 MPT_FILES_SRC_MPT += src/mpt/io_read/callbackstream.hpp
 MPT_FILES_SRC_MPT += src/mpt/io_read/filecursor.hpp
 MPT_FILES_SRC_MPT += src/mpt/io_read/filecursor_callbackstream.hpp
Index: build/vs2017winxp/libopenmpt-small.vcxproj
===================================================================
--- build/vs2017winxp/libopenmpt-small.vcxproj	(revision 17577)
+++ build/vs2017winxp/libopenmpt-small.vcxproj	(working copy)
@@ -844,6 +844,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2017winxp/libopenmpt-small.vcxproj.filters
===================================================================
--- build/vs2017winxp/libopenmpt-small.vcxproj.filters	(revision 17577)
+++ build/vs2017winxp/libopenmpt-small.vcxproj.filters	(working copy)
@@ -79,6 +79,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -771,6 +774,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2017winxp/libopenmpt.vcxproj
===================================================================
--- build/vs2017winxp/libopenmpt.vcxproj	(revision 17577)
+++ build/vs2017winxp/libopenmpt.vcxproj	(working copy)
@@ -844,6 +844,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2017winxp/libopenmpt.vcxproj.filters
===================================================================
--- build/vs2017winxp/libopenmpt.vcxproj.filters	(revision 17577)
+++ build/vs2017winxp/libopenmpt.vcxproj.filters	(working copy)
@@ -79,6 +79,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -771,6 +774,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2017winxp/libopenmpt_test.vcxproj
===================================================================
--- build/vs2017winxp/libopenmpt_test.vcxproj	(revision 17577)
+++ build/vs2017winxp/libopenmpt_test.vcxproj	(working copy)
@@ -820,6 +820,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2017winxp/libopenmpt_test.vcxproj.filters
===================================================================
--- build/vs2017winxp/libopenmpt_test.vcxproj.filters	(revision 17577)
+++ build/vs2017winxp/libopenmpt_test.vcxproj.filters	(working copy)
@@ -79,6 +79,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -777,6 +780,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2017winxp/OpenMPT-ANSI.vcxproj
===================================================================
--- build/vs2017winxp/OpenMPT-ANSI.vcxproj	(revision 17577)
+++ build/vs2017winxp/OpenMPT-ANSI.vcxproj	(working copy)
@@ -1073,6 +1073,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2017winxp/OpenMPT-ANSI.vcxproj.filters
===================================================================
--- build/vs2017winxp/OpenMPT-ANSI.vcxproj.filters	(revision 17577)
+++ build/vs2017winxp/OpenMPT-ANSI.vcxproj.filters	(working copy)
@@ -112,6 +112,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -1158,6 +1161,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2017winxp/OpenMPT-NativeSupport.vcxproj
===================================================================
--- build/vs2017winxp/OpenMPT-NativeSupport.vcxproj	(revision 17577)
+++ build/vs2017winxp/OpenMPT-NativeSupport.vcxproj	(working copy)
@@ -780,6 +780,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2017winxp/OpenMPT-NativeSupport.vcxproj.filters
===================================================================
--- build/vs2017winxp/OpenMPT-NativeSupport.vcxproj.filters	(revision 17577)
+++ build/vs2017winxp/OpenMPT-NativeSupport.vcxproj.filters	(working copy)
@@ -94,6 +94,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -612,6 +615,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2017winxp/OpenMPT-UTF8.vcxproj
===================================================================
--- build/vs2017winxp/OpenMPT-UTF8.vcxproj	(revision 17577)
+++ build/vs2017winxp/OpenMPT-UTF8.vcxproj	(working copy)
@@ -1073,6 +1073,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2017winxp/OpenMPT-UTF8.vcxproj.filters
===================================================================
--- build/vs2017winxp/OpenMPT-UTF8.vcxproj.filters	(revision 17577)
+++ build/vs2017winxp/OpenMPT-UTF8.vcxproj.filters	(working copy)
@@ -112,6 +112,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -1158,6 +1161,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2017winxp/OpenMPT.vcxproj
===================================================================
--- build/vs2017winxp/OpenMPT.vcxproj	(revision 17577)
+++ build/vs2017winxp/OpenMPT.vcxproj	(working copy)
@@ -1073,6 +1073,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2017winxp/OpenMPT.vcxproj.filters
===================================================================
--- build/vs2017winxp/OpenMPT.vcxproj.filters	(revision 17577)
+++ build/vs2017winxp/OpenMPT.vcxproj.filters	(working copy)
@@ -112,6 +112,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -1158,6 +1161,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2017winxp/PluginBridge.vcxproj
===================================================================
--- build/vs2017winxp/PluginBridge.vcxproj	(revision 17577)
+++ build/vs2017winxp/PluginBridge.vcxproj	(working copy)
@@ -820,6 +820,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2017winxp/PluginBridge.vcxproj.filters
===================================================================
--- build/vs2017winxp/PluginBridge.vcxproj.filters	(revision 17577)
+++ build/vs2017winxp/PluginBridge.vcxproj.filters	(working copy)
@@ -79,6 +79,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -459,6 +462,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2017winxp/PluginBridgeLegacy.vcxproj
===================================================================
--- build/vs2017winxp/PluginBridgeLegacy.vcxproj	(revision 17577)
+++ build/vs2017winxp/PluginBridgeLegacy.vcxproj	(working copy)
@@ -826,6 +826,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2017winxp/PluginBridgeLegacy.vcxproj.filters
===================================================================
--- build/vs2017winxp/PluginBridgeLegacy.vcxproj.filters	(revision 17577)
+++ build/vs2017winxp/PluginBridgeLegacy.vcxproj.filters	(working copy)
@@ -79,6 +79,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -459,6 +462,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2017winxp/updatesigntool.vcxproj
===================================================================
--- build/vs2017winxp/updatesigntool.vcxproj	(revision 17577)
+++ build/vs2017winxp/updatesigntool.vcxproj	(working copy)
@@ -753,6 +753,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2017winxp/updatesigntool.vcxproj.filters
===================================================================
--- build/vs2017winxp/updatesigntool.vcxproj.filters	(revision 17577)
+++ build/vs2017winxp/updatesigntool.vcxproj.filters	(working copy)
@@ -82,6 +82,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -552,6 +555,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2019win10/libopenmpt-small.vcxproj
===================================================================
--- build/vs2019win10/libopenmpt-small.vcxproj	(revision 17577)
+++ build/vs2019win10/libopenmpt-small.vcxproj	(working copy)
@@ -1465,6 +1465,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2019win10/libopenmpt-small.vcxproj.filters
===================================================================
--- build/vs2019win10/libopenmpt-small.vcxproj.filters	(revision 17577)
+++ build/vs2019win10/libopenmpt-small.vcxproj.filters	(working copy)
@@ -79,6 +79,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -771,6 +774,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2019win10/libopenmpt.vcxproj
===================================================================
--- build/vs2019win10/libopenmpt.vcxproj	(revision 17577)
+++ build/vs2019win10/libopenmpt.vcxproj	(working copy)
@@ -1465,6 +1465,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2019win10/libopenmpt.vcxproj.filters
===================================================================
--- build/vs2019win10/libopenmpt.vcxproj.filters	(revision 17577)
+++ build/vs2019win10/libopenmpt.vcxproj.filters	(working copy)
@@ -79,6 +79,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -771,6 +774,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2019win10/libopenmpt_test.vcxproj
===================================================================
--- build/vs2019win10/libopenmpt_test.vcxproj	(revision 17577)
+++ build/vs2019win10/libopenmpt_test.vcxproj	(working copy)
@@ -1417,6 +1417,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2019win10/libopenmpt_test.vcxproj.filters
===================================================================
--- build/vs2019win10/libopenmpt_test.vcxproj.filters	(revision 17577)
+++ build/vs2019win10/libopenmpt_test.vcxproj.filters	(working copy)
@@ -79,6 +79,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -777,6 +780,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2019win10/OpenMPT-ANSI.vcxproj
===================================================================
--- build/vs2019win10/OpenMPT-ANSI.vcxproj	(revision 17577)
+++ build/vs2019win10/OpenMPT-ANSI.vcxproj	(working copy)
@@ -1848,6 +1848,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2019win10/OpenMPT-ANSI.vcxproj.filters
===================================================================
--- build/vs2019win10/OpenMPT-ANSI.vcxproj.filters	(revision 17577)
+++ build/vs2019win10/OpenMPT-ANSI.vcxproj.filters	(working copy)
@@ -124,6 +124,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -1200,6 +1203,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2019win10/OpenMPT-NativeSupport.vcxproj
===================================================================
--- build/vs2019win10/OpenMPT-NativeSupport.vcxproj	(revision 17577)
+++ build/vs2019win10/OpenMPT-NativeSupport.vcxproj	(working copy)
@@ -1401,6 +1401,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2019win10/OpenMPT-NativeSupport.vcxproj.filters
===================================================================
--- build/vs2019win10/OpenMPT-NativeSupport.vcxproj.filters	(revision 17577)
+++ build/vs2019win10/OpenMPT-NativeSupport.vcxproj.filters	(working copy)
@@ -94,6 +94,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -612,6 +615,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2019win10/OpenMPT-UTF8.vcxproj
===================================================================
--- build/vs2019win10/OpenMPT-UTF8.vcxproj	(revision 17577)
+++ build/vs2019win10/OpenMPT-UTF8.vcxproj	(working copy)
@@ -1848,6 +1848,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2019win10/OpenMPT-UTF8.vcxproj.filters
===================================================================
--- build/vs2019win10/OpenMPT-UTF8.vcxproj.filters	(revision 17577)
+++ build/vs2019win10/OpenMPT-UTF8.vcxproj.filters	(working copy)
@@ -124,6 +124,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -1200,6 +1203,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2019win10/OpenMPT.vcxproj
===================================================================
--- build/vs2019win10/OpenMPT.vcxproj	(revision 17577)
+++ build/vs2019win10/OpenMPT.vcxproj	(working copy)
@@ -1848,6 +1848,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2019win10/OpenMPT.vcxproj.filters
===================================================================
--- build/vs2019win10/OpenMPT.vcxproj.filters	(revision 17577)
+++ build/vs2019win10/OpenMPT.vcxproj.filters	(working copy)
@@ -124,6 +124,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -1200,6 +1203,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2019win10/PluginBridge.vcxproj
===================================================================
--- build/vs2019win10/PluginBridge.vcxproj	(revision 17577)
+++ build/vs2019win10/PluginBridge.vcxproj	(working copy)
@@ -1525,6 +1525,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2019win10/PluginBridge.vcxproj.filters
===================================================================
--- build/vs2019win10/PluginBridge.vcxproj.filters	(revision 17577)
+++ build/vs2019win10/PluginBridge.vcxproj.filters	(working copy)
@@ -85,6 +85,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -465,6 +468,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2019win10/PluginBridgeLegacy.vcxproj
===================================================================
--- build/vs2019win10/PluginBridgeLegacy.vcxproj	(revision 17577)
+++ build/vs2019win10/PluginBridgeLegacy.vcxproj	(working copy)
@@ -1525,6 +1525,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2019win10/PluginBridgeLegacy.vcxproj.filters
===================================================================
--- build/vs2019win10/PluginBridgeLegacy.vcxproj.filters	(revision 17577)
+++ build/vs2019win10/PluginBridgeLegacy.vcxproj.filters	(working copy)
@@ -85,6 +85,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -465,6 +468,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2019win10/updatesigntool.vcxproj
===================================================================
--- build/vs2019win10/updatesigntool.vcxproj	(revision 17577)
+++ build/vs2019win10/updatesigntool.vcxproj	(working copy)
@@ -1362,6 +1362,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2019win10/updatesigntool.vcxproj.filters
===================================================================
--- build/vs2019win10/updatesigntool.vcxproj.filters	(revision 17577)
+++ build/vs2019win10/updatesigntool.vcxproj.filters	(working copy)
@@ -82,6 +82,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -552,6 +555,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2019win10uwp/libopenmpt-small.vcxproj
===================================================================
--- build/vs2019win10uwp/libopenmpt-small.vcxproj	(revision 17577)
+++ build/vs2019win10uwp/libopenmpt-small.vcxproj	(working copy)
@@ -1495,6 +1495,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2019win10uwp/libopenmpt-small.vcxproj.filters
===================================================================
--- build/vs2019win10uwp/libopenmpt-small.vcxproj.filters	(revision 17577)
+++ build/vs2019win10uwp/libopenmpt-small.vcxproj.filters	(working copy)
@@ -79,6 +79,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -771,6 +774,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2019win10uwp/libopenmpt.vcxproj
===================================================================
--- build/vs2019win10uwp/libopenmpt.vcxproj	(revision 17577)
+++ build/vs2019win10uwp/libopenmpt.vcxproj	(working copy)
@@ -1495,6 +1495,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2019win10uwp/libopenmpt.vcxproj.filters
===================================================================
--- build/vs2019win10uwp/libopenmpt.vcxproj.filters	(revision 17577)
+++ build/vs2019win10uwp/libopenmpt.vcxproj.filters	(working copy)
@@ -79,6 +79,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -771,6 +774,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2019win7/libopenmpt-small.vcxproj
===================================================================
--- build/vs2019win7/libopenmpt-small.vcxproj	(revision 17577)
+++ build/vs2019win7/libopenmpt-small.vcxproj	(working copy)
@@ -841,6 +841,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2019win7/libopenmpt-small.vcxproj.filters
===================================================================
--- build/vs2019win7/libopenmpt-small.vcxproj.filters	(revision 17577)
+++ build/vs2019win7/libopenmpt-small.vcxproj.filters	(working copy)
@@ -79,6 +79,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -771,6 +774,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2019win7/libopenmpt.vcxproj
===================================================================
--- build/vs2019win7/libopenmpt.vcxproj	(revision 17577)
+++ build/vs2019win7/libopenmpt.vcxproj	(working copy)
@@ -841,6 +841,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2019win7/libopenmpt.vcxproj.filters
===================================================================
--- build/vs2019win7/libopenmpt.vcxproj.filters	(revision 17577)
+++ build/vs2019win7/libopenmpt.vcxproj.filters	(working copy)
@@ -79,6 +79,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -771,6 +774,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2019win7/libopenmpt_test.vcxproj
===================================================================
--- build/vs2019win7/libopenmpt_test.vcxproj	(revision 17577)
+++ build/vs2019win7/libopenmpt_test.vcxproj	(working copy)
@@ -817,6 +817,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2019win7/libopenmpt_test.vcxproj.filters
===================================================================
--- build/vs2019win7/libopenmpt_test.vcxproj.filters	(revision 17577)
+++ build/vs2019win7/libopenmpt_test.vcxproj.filters	(working copy)
@@ -79,6 +79,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -777,6 +780,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2019win7/OpenMPT-ANSI.vcxproj
===================================================================
--- build/vs2019win7/OpenMPT-ANSI.vcxproj	(revision 17577)
+++ build/vs2019win7/OpenMPT-ANSI.vcxproj	(working copy)
@@ -1092,6 +1092,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2019win7/OpenMPT-ANSI.vcxproj.filters
===================================================================
--- build/vs2019win7/OpenMPT-ANSI.vcxproj.filters	(revision 17577)
+++ build/vs2019win7/OpenMPT-ANSI.vcxproj.filters	(working copy)
@@ -124,6 +124,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -1200,6 +1203,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2019win7/OpenMPT-NativeSupport.vcxproj
===================================================================
--- build/vs2019win7/OpenMPT-NativeSupport.vcxproj	(revision 17577)
+++ build/vs2019win7/OpenMPT-NativeSupport.vcxproj	(working copy)
@@ -777,6 +777,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2019win7/OpenMPT-NativeSupport.vcxproj.filters
===================================================================
--- build/vs2019win7/OpenMPT-NativeSupport.vcxproj.filters	(revision 17577)
+++ build/vs2019win7/OpenMPT-NativeSupport.vcxproj.filters	(working copy)
@@ -94,6 +94,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -612,6 +615,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2019win7/OpenMPT-UTF8.vcxproj
===================================================================
--- build/vs2019win7/OpenMPT-UTF8.vcxproj	(revision 17577)
+++ build/vs2019win7/OpenMPT-UTF8.vcxproj	(working copy)
@@ -1092,6 +1092,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2019win7/OpenMPT-UTF8.vcxproj.filters
===================================================================
--- build/vs2019win7/OpenMPT-UTF8.vcxproj.filters	(revision 17577)
+++ build/vs2019win7/OpenMPT-UTF8.vcxproj.filters	(working copy)
@@ -124,6 +124,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -1200,6 +1203,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2019win7/OpenMPT.vcxproj
===================================================================
--- build/vs2019win7/OpenMPT.vcxproj	(revision 17577)
+++ build/vs2019win7/OpenMPT.vcxproj	(working copy)
@@ -1092,6 +1092,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2019win7/OpenMPT.vcxproj.filters
===================================================================
--- build/vs2019win7/OpenMPT.vcxproj.filters	(revision 17577)
+++ build/vs2019win7/OpenMPT.vcxproj.filters	(working copy)
@@ -124,6 +124,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -1200,6 +1203,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2019win7/PluginBridge.vcxproj
===================================================================
--- build/vs2019win7/PluginBridge.vcxproj	(revision 17577)
+++ build/vs2019win7/PluginBridge.vcxproj	(working copy)
@@ -817,6 +817,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2019win7/PluginBridge.vcxproj.filters
===================================================================
--- build/vs2019win7/PluginBridge.vcxproj.filters	(revision 17577)
+++ build/vs2019win7/PluginBridge.vcxproj.filters	(working copy)
@@ -85,6 +85,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -465,6 +468,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2019win7/PluginBridgeLegacy.vcxproj
===================================================================
--- build/vs2019win7/PluginBridgeLegacy.vcxproj	(revision 17577)
+++ build/vs2019win7/PluginBridgeLegacy.vcxproj	(working copy)
@@ -823,6 +823,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2019win7/PluginBridgeLegacy.vcxproj.filters
===================================================================
--- build/vs2019win7/PluginBridgeLegacy.vcxproj.filters	(revision 17577)
+++ build/vs2019win7/PluginBridgeLegacy.vcxproj.filters	(working copy)
@@ -85,6 +85,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -465,6 +468,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2019win7/updatesigntool.vcxproj
===================================================================
--- build/vs2019win7/updatesigntool.vcxproj	(revision 17577)
+++ build/vs2019win7/updatesigntool.vcxproj	(working copy)
@@ -750,6 +750,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2019win7/updatesigntool.vcxproj.filters
===================================================================
--- build/vs2019win7/updatesigntool.vcxproj.filters	(revision 17577)
+++ build/vs2019win7/updatesigntool.vcxproj.filters	(working copy)
@@ -82,6 +82,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -552,6 +555,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2019win81/libopenmpt-small.vcxproj
===================================================================
--- build/vs2019win81/libopenmpt-small.vcxproj	(revision 17577)
+++ build/vs2019win81/libopenmpt-small.vcxproj	(working copy)
@@ -841,6 +841,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2019win81/libopenmpt-small.vcxproj.filters
===================================================================
--- build/vs2019win81/libopenmpt-small.vcxproj.filters	(revision 17577)
+++ build/vs2019win81/libopenmpt-small.vcxproj.filters	(working copy)
@@ -79,6 +79,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -771,6 +774,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2019win81/libopenmpt.vcxproj
===================================================================
--- build/vs2019win81/libopenmpt.vcxproj	(revision 17577)
+++ build/vs2019win81/libopenmpt.vcxproj	(working copy)
@@ -841,6 +841,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2019win81/libopenmpt.vcxproj.filters
===================================================================
--- build/vs2019win81/libopenmpt.vcxproj.filters	(revision 17577)
+++ build/vs2019win81/libopenmpt.vcxproj.filters	(working copy)
@@ -79,6 +79,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -771,6 +774,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2019win81/libopenmpt_test.vcxproj
===================================================================
--- build/vs2019win81/libopenmpt_test.vcxproj	(revision 17577)
+++ build/vs2019win81/libopenmpt_test.vcxproj	(working copy)
@@ -817,6 +817,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2019win81/libopenmpt_test.vcxproj.filters
===================================================================
--- build/vs2019win81/libopenmpt_test.vcxproj.filters	(revision 17577)
+++ build/vs2019win81/libopenmpt_test.vcxproj.filters	(working copy)
@@ -79,6 +79,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -777,6 +780,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2019win81/OpenMPT-ANSI.vcxproj
===================================================================
--- build/vs2019win81/OpenMPT-ANSI.vcxproj	(revision 17577)
+++ build/vs2019win81/OpenMPT-ANSI.vcxproj	(working copy)
@@ -1092,6 +1092,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2019win81/OpenMPT-ANSI.vcxproj.filters
===================================================================
--- build/vs2019win81/OpenMPT-ANSI.vcxproj.filters	(revision 17577)
+++ build/vs2019win81/OpenMPT-ANSI.vcxproj.filters	(working copy)
@@ -124,6 +124,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -1200,6 +1203,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2019win81/OpenMPT-NativeSupport.vcxproj
===================================================================
--- build/vs2019win81/OpenMPT-NativeSupport.vcxproj	(revision 17577)
+++ build/vs2019win81/OpenMPT-NativeSupport.vcxproj	(working copy)
@@ -777,6 +777,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2019win81/OpenMPT-NativeSupport.vcxproj.filters
===================================================================
--- build/vs2019win81/OpenMPT-NativeSupport.vcxproj.filters	(revision 17577)
+++ build/vs2019win81/OpenMPT-NativeSupport.vcxproj.filters	(working copy)
@@ -94,6 +94,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -612,6 +615,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2019win81/OpenMPT-UTF8.vcxproj
===================================================================
--- build/vs2019win81/OpenMPT-UTF8.vcxproj	(revision 17577)
+++ build/vs2019win81/OpenMPT-UTF8.vcxproj	(working copy)
@@ -1092,6 +1092,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2019win81/OpenMPT-UTF8.vcxproj.filters
===================================================================
--- build/vs2019win81/OpenMPT-UTF8.vcxproj.filters	(revision 17577)
+++ build/vs2019win81/OpenMPT-UTF8.vcxproj.filters	(working copy)
@@ -124,6 +124,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -1200,6 +1203,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2019win81/OpenMPT.vcxproj
===================================================================
--- build/vs2019win81/OpenMPT.vcxproj	(revision 17577)
+++ build/vs2019win81/OpenMPT.vcxproj	(working copy)
@@ -1092,6 +1092,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2019win81/OpenMPT.vcxproj.filters
===================================================================
--- build/vs2019win81/OpenMPT.vcxproj.filters	(revision 17577)
+++ build/vs2019win81/OpenMPT.vcxproj.filters	(working copy)
@@ -124,6 +124,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -1200,6 +1203,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2019win81/PluginBridge.vcxproj
===================================================================
--- build/vs2019win81/PluginBridge.vcxproj	(revision 17577)
+++ build/vs2019win81/PluginBridge.vcxproj	(working copy)
@@ -817,6 +817,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2019win81/PluginBridge.vcxproj.filters
===================================================================
--- build/vs2019win81/PluginBridge.vcxproj.filters	(revision 17577)
+++ build/vs2019win81/PluginBridge.vcxproj.filters	(working copy)
@@ -85,6 +85,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -465,6 +468,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2019win81/PluginBridgeLegacy.vcxproj
===================================================================
--- build/vs2019win81/PluginBridgeLegacy.vcxproj	(revision 17577)
+++ build/vs2019win81/PluginBridgeLegacy.vcxproj	(working copy)
@@ -823,6 +823,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2019win81/PluginBridgeLegacy.vcxproj.filters
===================================================================
--- build/vs2019win81/PluginBridgeLegacy.vcxproj.filters	(revision 17577)
+++ build/vs2019win81/PluginBridgeLegacy.vcxproj.filters	(working copy)
@@ -85,6 +85,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -465,6 +468,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2019win81/updatesigntool.vcxproj
===================================================================
--- build/vs2019win81/updatesigntool.vcxproj	(revision 17577)
+++ build/vs2019win81/updatesigntool.vcxproj	(working copy)
@@ -750,6 +750,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2019win81/updatesigntool.vcxproj.filters
===================================================================
--- build/vs2019win81/updatesigntool.vcxproj.filters	(revision 17577)
+++ build/vs2019win81/updatesigntool.vcxproj.filters	(working copy)
@@ -82,6 +82,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -552,6 +555,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win10/libopenmpt-small.vcxproj
===================================================================
--- build/vs2022win10/libopenmpt-small.vcxproj	(revision 17577)
+++ build/vs2022win10/libopenmpt-small.vcxproj	(working copy)
@@ -1489,6 +1489,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win10/libopenmpt-small.vcxproj.filters
===================================================================
--- build/vs2022win10/libopenmpt-small.vcxproj.filters	(revision 17577)
+++ build/vs2022win10/libopenmpt-small.vcxproj.filters	(working copy)
@@ -79,6 +79,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -771,6 +774,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win10/libopenmpt.vcxproj
===================================================================
--- build/vs2022win10/libopenmpt.vcxproj	(revision 17577)
+++ build/vs2022win10/libopenmpt.vcxproj	(working copy)
@@ -1489,6 +1489,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win10/libopenmpt.vcxproj.filters
===================================================================
--- build/vs2022win10/libopenmpt.vcxproj.filters	(revision 17577)
+++ build/vs2022win10/libopenmpt.vcxproj.filters	(working copy)
@@ -79,6 +79,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -771,6 +774,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win10/libopenmpt_test.vcxproj
===================================================================
--- build/vs2022win10/libopenmpt_test.vcxproj	(revision 17577)
+++ build/vs2022win10/libopenmpt_test.vcxproj	(working copy)
@@ -1441,6 +1441,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win10/libopenmpt_test.vcxproj.filters
===================================================================
--- build/vs2022win10/libopenmpt_test.vcxproj.filters	(revision 17577)
+++ build/vs2022win10/libopenmpt_test.vcxproj.filters	(working copy)
@@ -79,6 +79,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -777,6 +780,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win10/OpenMPT-ANSI.vcxproj
===================================================================
--- build/vs2022win10/OpenMPT-ANSI.vcxproj	(revision 17577)
+++ build/vs2022win10/OpenMPT-ANSI.vcxproj	(working copy)
@@ -1872,6 +1872,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win10/OpenMPT-ANSI.vcxproj.filters
===================================================================
--- build/vs2022win10/OpenMPT-ANSI.vcxproj.filters	(revision 17577)
+++ build/vs2022win10/OpenMPT-ANSI.vcxproj.filters	(working copy)
@@ -124,6 +124,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -1200,6 +1203,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win10/OpenMPT-NativeSupport.vcxproj
===================================================================
--- build/vs2022win10/OpenMPT-NativeSupport.vcxproj	(revision 17577)
+++ build/vs2022win10/OpenMPT-NativeSupport.vcxproj	(working copy)
@@ -1425,6 +1425,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win10/OpenMPT-NativeSupport.vcxproj.filters
===================================================================
--- build/vs2022win10/OpenMPT-NativeSupport.vcxproj.filters	(revision 17577)
+++ build/vs2022win10/OpenMPT-NativeSupport.vcxproj.filters	(working copy)
@@ -94,6 +94,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -612,6 +615,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win10/OpenMPT-UTF8.vcxproj
===================================================================
--- build/vs2022win10/OpenMPT-UTF8.vcxproj	(revision 17577)
+++ build/vs2022win10/OpenMPT-UTF8.vcxproj	(working copy)
@@ -1872,6 +1872,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win10/OpenMPT-UTF8.vcxproj.filters
===================================================================
--- build/vs2022win10/OpenMPT-UTF8.vcxproj.filters	(revision 17577)
+++ build/vs2022win10/OpenMPT-UTF8.vcxproj.filters	(working copy)
@@ -124,6 +124,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -1200,6 +1203,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win10/OpenMPT.vcxproj
===================================================================
--- build/vs2022win10/OpenMPT.vcxproj	(revision 17577)
+++ build/vs2022win10/OpenMPT.vcxproj	(working copy)
@@ -1872,6 +1872,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win10/OpenMPT.vcxproj.filters
===================================================================
--- build/vs2022win10/OpenMPT.vcxproj.filters	(revision 17577)
+++ build/vs2022win10/OpenMPT.vcxproj.filters	(working copy)
@@ -124,6 +124,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -1200,6 +1203,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win10/PluginBridge.vcxproj
===================================================================
--- build/vs2022win10/PluginBridge.vcxproj	(revision 17577)
+++ build/vs2022win10/PluginBridge.vcxproj	(working copy)
@@ -1549,6 +1549,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win10/PluginBridge.vcxproj.filters
===================================================================
--- build/vs2022win10/PluginBridge.vcxproj.filters	(revision 17577)
+++ build/vs2022win10/PluginBridge.vcxproj.filters	(working copy)
@@ -85,6 +85,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -465,6 +468,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win10/PluginBridgeLegacy.vcxproj
===================================================================
--- build/vs2022win10/PluginBridgeLegacy.vcxproj	(revision 17577)
+++ build/vs2022win10/PluginBridgeLegacy.vcxproj	(working copy)
@@ -1549,6 +1549,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win10/PluginBridgeLegacy.vcxproj.filters
===================================================================
--- build/vs2022win10/PluginBridgeLegacy.vcxproj.filters	(revision 17577)
+++ build/vs2022win10/PluginBridgeLegacy.vcxproj.filters	(working copy)
@@ -85,6 +85,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -465,6 +468,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win10/updatesigntool.vcxproj
===================================================================
--- build/vs2022win10/updatesigntool.vcxproj	(revision 17577)
+++ build/vs2022win10/updatesigntool.vcxproj	(working copy)
@@ -1386,6 +1386,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win10/updatesigntool.vcxproj.filters
===================================================================
--- build/vs2022win10/updatesigntool.vcxproj.filters	(revision 17577)
+++ build/vs2022win10/updatesigntool.vcxproj.filters	(working copy)
@@ -82,6 +82,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -552,6 +555,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win10clang/libopenmpt-small.vcxproj
===================================================================
--- build/vs2022win10clang/libopenmpt-small.vcxproj	(revision 17577)
+++ build/vs2022win10clang/libopenmpt-small.vcxproj	(working copy)
@@ -1423,6 +1423,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win10clang/libopenmpt-small.vcxproj.filters
===================================================================
--- build/vs2022win10clang/libopenmpt-small.vcxproj.filters	(revision 17577)
+++ build/vs2022win10clang/libopenmpt-small.vcxproj.filters	(working copy)
@@ -79,6 +79,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -771,6 +774,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win10clang/libopenmpt.vcxproj
===================================================================
--- build/vs2022win10clang/libopenmpt.vcxproj	(revision 17577)
+++ build/vs2022win10clang/libopenmpt.vcxproj	(working copy)
@@ -1423,6 +1423,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win10clang/libopenmpt.vcxproj.filters
===================================================================
--- build/vs2022win10clang/libopenmpt.vcxproj.filters	(revision 17577)
+++ build/vs2022win10clang/libopenmpt.vcxproj.filters	(working copy)
@@ -79,6 +79,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -771,6 +774,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win10clang/libopenmpt_test.vcxproj
===================================================================
--- build/vs2022win10clang/libopenmpt_test.vcxproj	(revision 17577)
+++ build/vs2022win10clang/libopenmpt_test.vcxproj	(working copy)
@@ -1375,6 +1375,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win10clang/libopenmpt_test.vcxproj.filters
===================================================================
--- build/vs2022win10clang/libopenmpt_test.vcxproj.filters	(revision 17577)
+++ build/vs2022win10clang/libopenmpt_test.vcxproj.filters	(working copy)
@@ -79,6 +79,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -777,6 +780,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win10clang/OpenMPT-ANSI.vcxproj
===================================================================
--- build/vs2022win10clang/OpenMPT-ANSI.vcxproj	(revision 17577)
+++ build/vs2022win10clang/OpenMPT-ANSI.vcxproj	(working copy)
@@ -1854,6 +1854,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win10clang/OpenMPT-ANSI.vcxproj.filters
===================================================================
--- build/vs2022win10clang/OpenMPT-ANSI.vcxproj.filters	(revision 17577)
+++ build/vs2022win10clang/OpenMPT-ANSI.vcxproj.filters	(working copy)
@@ -124,6 +124,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -1200,6 +1203,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win10clang/OpenMPT-NativeSupport.vcxproj
===================================================================
--- build/vs2022win10clang/OpenMPT-NativeSupport.vcxproj	(revision 17577)
+++ build/vs2022win10clang/OpenMPT-NativeSupport.vcxproj	(working copy)
@@ -1359,6 +1359,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win10clang/OpenMPT-NativeSupport.vcxproj.filters
===================================================================
--- build/vs2022win10clang/OpenMPT-NativeSupport.vcxproj.filters	(revision 17577)
+++ build/vs2022win10clang/OpenMPT-NativeSupport.vcxproj.filters	(working copy)
@@ -94,6 +94,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -612,6 +615,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win10clang/OpenMPT-UTF8.vcxproj
===================================================================
--- build/vs2022win10clang/OpenMPT-UTF8.vcxproj	(revision 17577)
+++ build/vs2022win10clang/OpenMPT-UTF8.vcxproj	(working copy)
@@ -1854,6 +1854,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win10clang/OpenMPT-UTF8.vcxproj.filters
===================================================================
--- build/vs2022win10clang/OpenMPT-UTF8.vcxproj.filters	(revision 17577)
+++ build/vs2022win10clang/OpenMPT-UTF8.vcxproj.filters	(working copy)
@@ -124,6 +124,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -1200,6 +1203,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win10clang/OpenMPT.vcxproj
===================================================================
--- build/vs2022win10clang/OpenMPT.vcxproj	(revision 17577)
+++ build/vs2022win10clang/OpenMPT.vcxproj	(working copy)
@@ -1854,6 +1854,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win10clang/OpenMPT.vcxproj.filters
===================================================================
--- build/vs2022win10clang/OpenMPT.vcxproj.filters	(revision 17577)
+++ build/vs2022win10clang/OpenMPT.vcxproj.filters	(working copy)
@@ -124,6 +124,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -1200,6 +1203,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win10clang/PluginBridge.vcxproj
===================================================================
--- build/vs2022win10clang/PluginBridge.vcxproj	(revision 17577)
+++ build/vs2022win10clang/PluginBridge.vcxproj	(working copy)
@@ -1483,6 +1483,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win10clang/PluginBridge.vcxproj.filters
===================================================================
--- build/vs2022win10clang/PluginBridge.vcxproj.filters	(revision 17577)
+++ build/vs2022win10clang/PluginBridge.vcxproj.filters	(working copy)
@@ -85,6 +85,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -465,6 +468,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win10clang/PluginBridgeLegacy.vcxproj
===================================================================
--- build/vs2022win10clang/PluginBridgeLegacy.vcxproj	(revision 17577)
+++ build/vs2022win10clang/PluginBridgeLegacy.vcxproj	(working copy)
@@ -1483,6 +1483,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win10clang/PluginBridgeLegacy.vcxproj.filters
===================================================================
--- build/vs2022win10clang/PluginBridgeLegacy.vcxproj.filters	(revision 17577)
+++ build/vs2022win10clang/PluginBridgeLegacy.vcxproj.filters	(working copy)
@@ -85,6 +85,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -465,6 +468,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win10clang/updatesigntool.vcxproj
===================================================================
--- build/vs2022win10clang/updatesigntool.vcxproj	(revision 17577)
+++ build/vs2022win10clang/updatesigntool.vcxproj	(working copy)
@@ -1320,6 +1320,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win10clang/updatesigntool.vcxproj.filters
===================================================================
--- build/vs2022win10clang/updatesigntool.vcxproj.filters	(revision 17577)
+++ build/vs2022win10clang/updatesigntool.vcxproj.filters	(working copy)
@@ -82,6 +82,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -552,6 +555,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win10uwp/libopenmpt-small.vcxproj
===================================================================
--- build/vs2022win10uwp/libopenmpt-small.vcxproj	(revision 17577)
+++ build/vs2022win10uwp/libopenmpt-small.vcxproj	(working copy)
@@ -1519,6 +1519,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win10uwp/libopenmpt-small.vcxproj.filters
===================================================================
--- build/vs2022win10uwp/libopenmpt-small.vcxproj.filters	(revision 17577)
+++ build/vs2022win10uwp/libopenmpt-small.vcxproj.filters	(working copy)
@@ -79,6 +79,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -771,6 +774,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win10uwp/libopenmpt.vcxproj
===================================================================
--- build/vs2022win10uwp/libopenmpt.vcxproj	(revision 17577)
+++ build/vs2022win10uwp/libopenmpt.vcxproj	(working copy)
@@ -1519,6 +1519,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win10uwp/libopenmpt.vcxproj.filters
===================================================================
--- build/vs2022win10uwp/libopenmpt.vcxproj.filters	(revision 17577)
+++ build/vs2022win10uwp/libopenmpt.vcxproj.filters	(working copy)
@@ -79,6 +79,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -771,6 +774,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win7/libopenmpt-small.vcxproj
===================================================================
--- build/vs2022win7/libopenmpt-small.vcxproj	(revision 17577)
+++ build/vs2022win7/libopenmpt-small.vcxproj	(working copy)
@@ -853,6 +853,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win7/libopenmpt-small.vcxproj.filters
===================================================================
--- build/vs2022win7/libopenmpt-small.vcxproj.filters	(revision 17577)
+++ build/vs2022win7/libopenmpt-small.vcxproj.filters	(working copy)
@@ -79,6 +79,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -771,6 +774,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win7/libopenmpt.vcxproj
===================================================================
--- build/vs2022win7/libopenmpt.vcxproj	(revision 17577)
+++ build/vs2022win7/libopenmpt.vcxproj	(working copy)
@@ -853,6 +853,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win7/libopenmpt.vcxproj.filters
===================================================================
--- build/vs2022win7/libopenmpt.vcxproj.filters	(revision 17577)
+++ build/vs2022win7/libopenmpt.vcxproj.filters	(working copy)
@@ -79,6 +79,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -771,6 +774,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win7/libopenmpt_test.vcxproj
===================================================================
--- build/vs2022win7/libopenmpt_test.vcxproj	(revision 17577)
+++ build/vs2022win7/libopenmpt_test.vcxproj	(working copy)
@@ -829,6 +829,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win7/libopenmpt_test.vcxproj.filters
===================================================================
--- build/vs2022win7/libopenmpt_test.vcxproj.filters	(revision 17577)
+++ build/vs2022win7/libopenmpt_test.vcxproj.filters	(working copy)
@@ -79,6 +79,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -777,6 +780,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win7/OpenMPT-ANSI.vcxproj
===================================================================
--- build/vs2022win7/OpenMPT-ANSI.vcxproj	(revision 17577)
+++ build/vs2022win7/OpenMPT-ANSI.vcxproj	(working copy)
@@ -1104,6 +1104,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win7/OpenMPT-ANSI.vcxproj.filters
===================================================================
--- build/vs2022win7/OpenMPT-ANSI.vcxproj.filters	(revision 17577)
+++ build/vs2022win7/OpenMPT-ANSI.vcxproj.filters	(working copy)
@@ -124,6 +124,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -1200,6 +1203,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win7/OpenMPT-NativeSupport.vcxproj
===================================================================
--- build/vs2022win7/OpenMPT-NativeSupport.vcxproj	(revision 17577)
+++ build/vs2022win7/OpenMPT-NativeSupport.vcxproj	(working copy)
@@ -789,6 +789,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win7/OpenMPT-NativeSupport.vcxproj.filters
===================================================================
--- build/vs2022win7/OpenMPT-NativeSupport.vcxproj.filters	(revision 17577)
+++ build/vs2022win7/OpenMPT-NativeSupport.vcxproj.filters	(working copy)
@@ -94,6 +94,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -612,6 +615,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win7/OpenMPT-UTF8.vcxproj
===================================================================
--- build/vs2022win7/OpenMPT-UTF8.vcxproj	(revision 17577)
+++ build/vs2022win7/OpenMPT-UTF8.vcxproj	(working copy)
@@ -1104,6 +1104,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win7/OpenMPT-UTF8.vcxproj.filters
===================================================================
--- build/vs2022win7/OpenMPT-UTF8.vcxproj.filters	(revision 17577)
+++ build/vs2022win7/OpenMPT-UTF8.vcxproj.filters	(working copy)
@@ -124,6 +124,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -1200,6 +1203,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win7/OpenMPT.vcxproj
===================================================================
--- build/vs2022win7/OpenMPT.vcxproj	(revision 17577)
+++ build/vs2022win7/OpenMPT.vcxproj	(working copy)
@@ -1104,6 +1104,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win7/OpenMPT.vcxproj.filters
===================================================================
--- build/vs2022win7/OpenMPT.vcxproj.filters	(revision 17577)
+++ build/vs2022win7/OpenMPT.vcxproj.filters	(working copy)
@@ -124,6 +124,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -1200,6 +1203,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win7/PluginBridge.vcxproj
===================================================================
--- build/vs2022win7/PluginBridge.vcxproj	(revision 17577)
+++ build/vs2022win7/PluginBridge.vcxproj	(working copy)
@@ -829,6 +829,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win7/PluginBridge.vcxproj.filters
===================================================================
--- build/vs2022win7/PluginBridge.vcxproj.filters	(revision 17577)
+++ build/vs2022win7/PluginBridge.vcxproj.filters	(working copy)
@@ -85,6 +85,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -465,6 +468,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win7/PluginBridgeLegacy.vcxproj
===================================================================
--- build/vs2022win7/PluginBridgeLegacy.vcxproj	(revision 17577)
+++ build/vs2022win7/PluginBridgeLegacy.vcxproj	(working copy)
@@ -835,6 +835,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win7/PluginBridgeLegacy.vcxproj.filters
===================================================================
--- build/vs2022win7/PluginBridgeLegacy.vcxproj.filters	(revision 17577)
+++ build/vs2022win7/PluginBridgeLegacy.vcxproj.filters	(working copy)
@@ -85,6 +85,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -465,6 +468,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win7/updatesigntool.vcxproj
===================================================================
--- build/vs2022win7/updatesigntool.vcxproj	(revision 17577)
+++ build/vs2022win7/updatesigntool.vcxproj	(working copy)
@@ -762,6 +762,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win7/updatesigntool.vcxproj.filters
===================================================================
--- build/vs2022win7/updatesigntool.vcxproj.filters	(revision 17577)
+++ build/vs2022win7/updatesigntool.vcxproj.filters	(working copy)
@@ -82,6 +82,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -552,6 +555,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win81/libopenmpt-small.vcxproj
===================================================================
--- build/vs2022win81/libopenmpt-small.vcxproj	(revision 17577)
+++ build/vs2022win81/libopenmpt-small.vcxproj	(working copy)
@@ -853,6 +853,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win81/libopenmpt-small.vcxproj.filters
===================================================================
--- build/vs2022win81/libopenmpt-small.vcxproj.filters	(revision 17577)
+++ build/vs2022win81/libopenmpt-small.vcxproj.filters	(working copy)
@@ -79,6 +79,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -771,6 +774,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win81/libopenmpt.vcxproj
===================================================================
--- build/vs2022win81/libopenmpt.vcxproj	(revision 17577)
+++ build/vs2022win81/libopenmpt.vcxproj	(working copy)
@@ -853,6 +853,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win81/libopenmpt.vcxproj.filters
===================================================================
--- build/vs2022win81/libopenmpt.vcxproj.filters	(revision 17577)
+++ build/vs2022win81/libopenmpt.vcxproj.filters	(working copy)
@@ -79,6 +79,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -771,6 +774,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win81/libopenmpt_test.vcxproj
===================================================================
--- build/vs2022win81/libopenmpt_test.vcxproj	(revision 17577)
+++ build/vs2022win81/libopenmpt_test.vcxproj	(working copy)
@@ -829,6 +829,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win81/libopenmpt_test.vcxproj.filters
===================================================================
--- build/vs2022win81/libopenmpt_test.vcxproj.filters	(revision 17577)
+++ build/vs2022win81/libopenmpt_test.vcxproj.filters	(working copy)
@@ -79,6 +79,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -777,6 +780,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win81/OpenMPT-ANSI.vcxproj
===================================================================
--- build/vs2022win81/OpenMPT-ANSI.vcxproj	(revision 17577)
+++ build/vs2022win81/OpenMPT-ANSI.vcxproj	(working copy)
@@ -1104,6 +1104,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win81/OpenMPT-ANSI.vcxproj.filters
===================================================================
--- build/vs2022win81/OpenMPT-ANSI.vcxproj.filters	(revision 17577)
+++ build/vs2022win81/OpenMPT-ANSI.vcxproj.filters	(working copy)
@@ -124,6 +124,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -1200,6 +1203,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win81/OpenMPT-NativeSupport.vcxproj
===================================================================
--- build/vs2022win81/OpenMPT-NativeSupport.vcxproj	(revision 17577)
+++ build/vs2022win81/OpenMPT-NativeSupport.vcxproj	(working copy)
@@ -789,6 +789,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win81/OpenMPT-NativeSupport.vcxproj.filters
===================================================================
--- build/vs2022win81/OpenMPT-NativeSupport.vcxproj.filters	(revision 17577)
+++ build/vs2022win81/OpenMPT-NativeSupport.vcxproj.filters	(working copy)
@@ -94,6 +94,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -612,6 +615,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win81/OpenMPT-UTF8.vcxproj
===================================================================
--- build/vs2022win81/OpenMPT-UTF8.vcxproj	(revision 17577)
+++ build/vs2022win81/OpenMPT-UTF8.vcxproj	(working copy)
@@ -1104,6 +1104,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win81/OpenMPT-UTF8.vcxproj.filters
===================================================================
--- build/vs2022win81/OpenMPT-UTF8.vcxproj.filters	(revision 17577)
+++ build/vs2022win81/OpenMPT-UTF8.vcxproj.filters	(working copy)
@@ -124,6 +124,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -1200,6 +1203,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win81/OpenMPT.vcxproj
===================================================================
--- build/vs2022win81/OpenMPT.vcxproj	(revision 17577)
+++ build/vs2022win81/OpenMPT.vcxproj	(working copy)
@@ -1104,6 +1104,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win81/OpenMPT.vcxproj.filters
===================================================================
--- build/vs2022win81/OpenMPT.vcxproj.filters	(revision 17577)
+++ build/vs2022win81/OpenMPT.vcxproj.filters	(working copy)
@@ -124,6 +124,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -1200,6 +1203,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win81/PluginBridge.vcxproj
===================================================================
--- build/vs2022win81/PluginBridge.vcxproj	(revision 17577)
+++ build/vs2022win81/PluginBridge.vcxproj	(working copy)
@@ -829,6 +829,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win81/PluginBridge.vcxproj.filters
===================================================================
--- build/vs2022win81/PluginBridge.vcxproj.filters	(revision 17577)
+++ build/vs2022win81/PluginBridge.vcxproj.filters	(working copy)
@@ -85,6 +85,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -465,6 +468,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win81/PluginBridgeLegacy.vcxproj
===================================================================
--- build/vs2022win81/PluginBridgeLegacy.vcxproj	(revision 17577)
+++ build/vs2022win81/PluginBridgeLegacy.vcxproj	(working copy)
@@ -835,6 +835,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win81/PluginBridgeLegacy.vcxproj.filters
===================================================================
--- build/vs2022win81/PluginBridgeLegacy.vcxproj.filters	(revision 17577)
+++ build/vs2022win81/PluginBridgeLegacy.vcxproj.filters	(working copy)
@@ -85,6 +85,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -465,6 +468,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/vs2022win81/updatesigntool.vcxproj
===================================================================
--- build/vs2022win81/updatesigntool.vcxproj	(revision 17577)
+++ build/vs2022win81/updatesigntool.vcxproj	(working copy)
@@ -762,6 +762,14 @@
     <ClInclude Include="..\..\src\mpt\io\io_stdstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io\io_virtual_wrapper.hpp" />
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp" />
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor.hpp" />
     <ClInclude Include="..\..\src\mpt\io_read\filecursor_callbackstream.hpp" />
Index: build/vs2022win81/updatesigntool.vcxproj.filters
===================================================================
--- build/vs2022win81/updatesigntool.vcxproj.filters	(revision 17577)
+++ build/vs2022win81/updatesigntool.vcxproj.filters	(working copy)
@@ -82,6 +82,9 @@
     <Filter Include="src\mpt\io\tests">
       <UniqueIdentifier>{B68E2391-A287-42E3-CBB9-2768B71122EF}</UniqueIdentifier>
     </Filter>
+    <Filter Include="src\mpt\io_file">
+      <UniqueIdentifier>{D34C5655-3FD8-C319-48E9-460EB4F31F1A}</UniqueIdentifier>
+    </Filter>
     <Filter Include="src\mpt\io_read">
       <UniqueIdentifier>{EFCE5C55-5B5A-CA19-646B-4D0ED075261A}</UniqueIdentifier>
     </Filter>
@@ -552,6 +555,30 @@
     <ClInclude Include="..\..\src\mpt\io\tests\tests_io.hpp">
       <Filter>src\mpt\io\tests</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileadapter.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fileref.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\fstream.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\inputfile_filecursor.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\outputfile.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_basename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\mpt\io_file\unique_tempfilename.hpp">
+      <Filter>src\mpt\io_file</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\mpt\io_read\callbackstream.hpp">
       <Filter>src\mpt\io_read</Filter>
     </ClInclude>
Index: build/wine/build_wine_support.cmd
===================================================================
--- build/wine/build_wine_support.cmd	(revision 17577)
+++ build/wine/build_wine_support.cmd	(working copy)
@@ -47,6 +47,7 @@
  src\mpt\fs\*.hpp ^
  src\mpt\io\*.hpp ^
  src\mpt\io\tests\*.hpp ^
+ src\mpt\io_file\*.hpp ^
  src\mpt\io_read\*.hpp ^
  src\mpt\io_write\*.hpp ^
  src\mpt\json\*.hpp ^
Index: build/xcode-ios/libopenmpt.xcodeproj/project.pbxproj
===================================================================
--- build/xcode-ios/libopenmpt.xcodeproj/project.pbxproj	(revision 17577)
+++ build/xcode-ios/libopenmpt.xcodeproj/project.pbxproj	(working copy)
@@ -256,6 +256,7 @@
 		20B693A5E7448B175546B1E5 /* MixerSettings.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MixerSettings.h; path = ../../soundlib/MixerSettings.h; sourceTree = "<group>"; };
 		212A36FD7B2B4DEF4272453D /* semantic_version.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = semantic_version.hpp; path = ../../src/mpt/base/semantic_version.hpp; sourceTree = "<group>"; };
 		2151B0037C9303754FED4E43 /* Mixer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Mixer.h; path = ../../soundlib/Mixer.h; sourceTree = "<group>"; };
+		22594181666D12B35A3E97C1 /* fileref.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = fileref.hpp; path = ../../src/mpt/io_file/fileref.hpp; sourceTree = "<group>"; };
 		2323E4A5944685974A0D32E5 /* Snd_defs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Snd_defs.h; path = ../../soundlib/Snd_defs.h; sourceTree = "<group>"; };
 		236E8DFB1D304A6D572F4C3B /* Tagging.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Tagging.h; path = ../../soundlib/Tagging.h; sourceTree = "<group>"; };
 		23EF16CF7F4F3B81C192DD0F /* filedata_base_buffered.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = filedata_base_buffered.hpp; path = ../../src/mpt/io_read/filedata_base_buffered.hpp; sourceTree = "<group>"; };
@@ -278,6 +279,7 @@
 		3286C74DF54727BF527C058D /* Flanger.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Flanger.cpp; path = ../../soundlib/plugins/dmo/Flanger.cpp; sourceTree = "<group>"; };
 		32BAAF6B89D057DDB618EDAB /* tests_base_saturate_cast.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = tests_base_saturate_cast.hpp; path = ../../src/mpt/base/tests/tests_base_saturate_cast.hpp; sourceTree = "<group>"; };
 		338C775D77DB1CCFBA02559D /* BitReader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BitReader.h; path = ../../soundlib/BitReader.h; sourceTree = "<group>"; };
+		34AF58C934523BFB6EA6EF09 /* fileadapter.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = fileadapter.hpp; path = ../../src/mpt/io_file/fileadapter.hpp; sourceTree = "<group>"; };
 		34D45985C7622377303AC7C5 /* Load_imf.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_imf.cpp; path = ../../soundlib/Load_imf.cpp; sourceTree = "<group>"; };
 		352908E1F7E96953551E4721 /* libopenmpt_c.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = libopenmpt_c.cpp; path = ../../libopenmpt/libopenmpt_c.cpp; sourceTree = "<group>"; };
 		36CAA59BC9586F8D323113DB /* Load_dmf.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_dmf.cpp; path = ../../soundlib/Load_dmf.cpp; sourceTree = "<group>"; };
@@ -285,6 +287,7 @@
 		37F80675CA85D067335E74B5 /* Load_amf.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_amf.cpp; path = ../../soundlib/Load_amf.cpp; sourceTree = "<group>"; };
 		38019939FAC1F9AB57F6D779 /* ContainerPP20.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ContainerPP20.cpp; path = ../../soundlib/ContainerPP20.cpp; sourceTree = "<group>"; };
 		392551FD7D73F76FBF9B303D /* ModSample.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ModSample.h; path = ../../soundlib/ModSample.h; sourceTree = "<group>"; };
+		394347E11EDA4F13E21F5E21 /* unique_tempfilename.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = unique_tempfilename.hpp; path = ../../src/mpt/io_file/unique_tempfilename.hpp; sourceTree = "<group>"; };
 		3947FF534AFF3F45C06D2D93 /* AudioCriticalSection.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = AudioCriticalSection.cpp; path = ../../soundlib/AudioCriticalSection.cpp; sourceTree = "<group>"; };
 		39C1318B738BE83D28E737CB /* filecursor_filename_traits.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = filecursor_filename_traits.hpp; path = ../../src/mpt/io_read/filecursor_filename_traits.hpp; sourceTree = "<group>"; };
 		39CF376FCC5D01613535A5AF /* FloatMixer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = FloatMixer.h; path = ../../soundlib/FloatMixer.h; sourceTree = "<group>"; };
@@ -346,6 +349,7 @@
 		597941C14D788B33D263A001 /* libopenmpt_cxx.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = libopenmpt_cxx.cpp; path = ../../libopenmpt/libopenmpt_cxx.cpp; sourceTree = "<group>"; };
 		5AF83E8DC8204CFFC9B33CCD /* pointer.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = pointer.hpp; path = ../../src/mpt/base/pointer.hpp; sourceTree = "<group>"; };
 		5BE26A37EE7034295748D877 /* modcommand.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = modcommand.h; path = ../../soundlib/modcommand.h; sourceTree = "<group>"; };
+		5C38D0E103AE6C93AC737721 /* inputfile_filecursor.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = inputfile_filecursor.hpp; path = ../../src/mpt/io_file/inputfile_filecursor.hpp; sourceTree = "<group>"; };
 		5CDA3BEFB6DB52E17E224A2F /* SampleFormatVorbis.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = SampleFormatVorbis.cpp; path = ../../soundlib/SampleFormatVorbis.cpp; sourceTree = "<group>"; };
 		5CFFC0612B231413B23166A1 /* parse.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = parse.hpp; path = ../../src/mpt/parse/parse.hpp; sourceTree = "<group>"; };
 		5D6A74BCEFF83EAE58D0E2FC /* mptRandom.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = mptRandom.cpp; path = ../../common/mptRandom.cpp; sourceTree = "<group>"; };
@@ -425,6 +429,7 @@
 		8B1AB3CB32904F7DDB555A0B /* filecursor_stdstream.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = filecursor_stdstream.hpp; path = ../../src/mpt/io_read/filecursor_stdstream.hpp; sourceTree = "<group>"; };
 		8BE3D4DDCE50214F8676931D /* libopenmpt_stream_callbacks_file_posix.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = libopenmpt_stream_callbacks_file_posix.h; path = ../../libopenmpt/libopenmpt_stream_callbacks_file_posix.h; sourceTree = "<group>"; };
 		8BE9A4117CE11203E8E49251 /* patternContainer.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = patternContainer.cpp; path = ../../soundlib/patternContainer.cpp; sourceTree = "<group>"; };
+		8BFBDA8B261FCFBDD645B0CB /* unique_basename.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = unique_basename.hpp; path = ../../src/mpt/io_file/unique_basename.hpp; sourceTree = "<group>"; };
 		8C3D549D4EFDB50FAC3292DD /* ParamEq.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ParamEq.cpp; path = ../../soundlib/plugins/dmo/ParamEq.cpp; sourceTree = "<group>"; };
 		8C8CA6B3531A9E25C11CC4F3 /* ModSampleCopy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ModSampleCopy.h; path = ../../soundlib/ModSampleCopy.h; sourceTree = "<group>"; };
 		8CA9A5C72521933951088407 /* simple_floatingpoint.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = simple_floatingpoint.hpp; path = ../../src/mpt/format/simple_floatingpoint.hpp; sourceTree = "<group>"; };
@@ -459,6 +464,7 @@
 		9E737DF712CDC2E990B14C37 /* Distortion.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Distortion.h; path = ../../soundlib/plugins/dmo/Distortion.h; sourceTree = "<group>"; };
 		9E8AC865F88BDF57BFD2D6A5 /* tests_binary.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = tests_binary.hpp; path = ../../src/mpt/binary/tests/tests_binary.hpp; sourceTree = "<group>"; };
 		9EB0D073130B156590EE9EB3 /* Compressor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Compressor.h; path = ../../soundlib/plugins/dmo/Compressor.h; sourceTree = "<group>"; };
+		9F187E69B9E4ED1BBDB584A9 /* outputfile.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = outputfile.hpp; path = ../../src/mpt/io_file/outputfile.hpp; sourceTree = "<group>"; };
 		9FCB791A628BD98CBFC0B75A /* dos_memory.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = dos_memory.hpp; path = ../../src/mpt/osinfo/dos_memory.hpp; sourceTree = "<group>"; };
 		A0ADCD61E4FC72D32723ABA1 /* MPEGFrame.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MPEGFrame.h; path = ../../soundlib/MPEGFrame.h; sourceTree = "<group>"; };
 		A105879DAD903A0F287505DD /* source_location.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = source_location.hpp; path = ../../src/mpt/base/source_location.hpp; sourceTree = "<group>"; };
@@ -519,6 +525,7 @@
 		CB4660EB0F95065D51BC3F2B /* pattern.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = pattern.cpp; path = ../../soundlib/pattern.cpp; sourceTree = "<group>"; };
 		CB69F5BB3FC43AADBDA7C3FB /* patternContainer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = patternContainer.h; path = ../../soundlib/patternContainer.h; sourceTree = "<group>"; };
 		CC1927E18ED98853EC0E6621 /* ModInstrument.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ModInstrument.cpp; path = ../../soundlib/ModInstrument.cpp; sourceTree = "<group>"; };
+		CE0AB78B121E88BD05F00DCB /* fstream.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = fstream.hpp; path = ../../src/mpt/io_file/fstream.hpp; sourceTree = "<group>"; };
 		CE271C83BB8EAF355888A2C3 /* filedata_stdstream.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = filedata_stdstream.hpp; path = ../../src/mpt/io_read/filedata_stdstream.hpp; sourceTree = "<group>"; };
 		CE32D5B4DABD882655A253F4 /* PluginManager.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = PluginManager.cpp; path = ../../soundlib/plugins/PluginManager.cpp; sourceTree = "<group>"; };
 		CE6385639C86D91523952BA3 /* split.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = split.hpp; path = ../../src/mpt/parse/split.hpp; sourceTree = "<group>"; };
@@ -585,6 +592,7 @@
 		FB635E3D352E14EFEA89647D /* filecursor_traits_filedata.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = filecursor_traits_filedata.hpp; path = ../../src/mpt/io_read/filecursor_traits_filedata.hpp; sourceTree = "<group>"; };
 		FBE197BEECD905B058DC85FE /* serialization_utils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = serialization_utils.h; path = ../../common/serialization_utils.h; sourceTree = "<group>"; };
 		FBF22E79CF82B5EBC2544CB9 /* libopenmpt_stream_callbacks_file_posix_lfs64.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = libopenmpt_stream_callbacks_file_posix_lfs64.h; path = ../../libopenmpt/libopenmpt_stream_callbacks_file_posix_lfs64.h; sourceTree = "<group>"; };
+		FC85D407DBA2EE39B7AC4A47 /* inputfile.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = inputfile.hpp; path = ../../src/mpt/io_file/inputfile.hpp; sourceTree = "<group>"; };
 		FED01DA32FB4159542CC4BE3 /* tuning.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = tuning.h; path = ../../soundlib/tuning.h; sourceTree = "<group>"; };
 		FEDB3A1791690409FA41A857 /* Echo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Echo.h; path = ../../soundlib/plugins/dmo/Echo.h; sourceTree = "<group>"; };
 		FF7F8F60F37ED8D27869EDA0 /* mptFileTemporary.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = mptFileTemporary.cpp; path = ../../common/mptFileTemporary.cpp; sourceTree = "<group>"; };
@@ -708,6 +716,21 @@
 			name = plugins;
 			sourceTree = "<group>";
 		};
+		3C7CE88D62A3183FC9AA0ECD /* io_file */ = {
+			isa = PBXGroup;
+			children = (
+				34AF58C934523BFB6EA6EF09 /* fileadapter.hpp */,
+				22594181666D12B35A3E97C1 /* fileref.hpp */,
+				CE0AB78B121E88BD05F00DCB /* fstream.hpp */,
+				FC85D407DBA2EE39B7AC4A47 /* inputfile.hpp */,
+				5C38D0E103AE6C93AC737721 /* inputfile_filecursor.hpp */,
+				9F187E69B9E4ED1BBDB584A9 /* outputfile.hpp */,
+				8BFBDA8B261FCFBDD645B0CB /* unique_basename.hpp */,
+				394347E11EDA4F13E21F5E21 /* unique_tempfilename.hpp */,
+			);
+			name = io_file;
+			sourceTree = "<group>";
+		};
 		42C3CDB59951382771CA4BF5 /* tests */ = {
 			isa = PBXGroup;
 			children = (
@@ -926,6 +949,7 @@
 				C2D2C30558498237C6E5D945 /* exception_text */,
 				4ECA2D3165DCC86355F8C371 /* format */,
 				1063AA8FB3AF33C1CE8000CF /* io */,
+				3C7CE88D62A3183FC9AA0ECD /* io_file */,
 				FB1C5B4521428AF788498185 /* io_read */,
 				54C514A39D35F8D5E1D4CAE3 /* io_write */,
 				8FB69E0532A4C4B7C1CAA445 /* mutex */,
Index: build/xcode-macosx/libopenmpt.xcodeproj/project.pbxproj
===================================================================
--- build/xcode-macosx/libopenmpt.xcodeproj/project.pbxproj	(revision 17577)
+++ build/xcode-macosx/libopenmpt.xcodeproj/project.pbxproj	(working copy)
@@ -256,6 +256,7 @@
 		20B693A5E7448B175546B1E5 /* MixerSettings.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MixerSettings.h; path = ../../soundlib/MixerSettings.h; sourceTree = "<group>"; };
 		212A36FD7B2B4DEF4272453D /* semantic_version.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = semantic_version.hpp; path = ../../src/mpt/base/semantic_version.hpp; sourceTree = "<group>"; };
 		2151B0037C9303754FED4E43 /* Mixer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Mixer.h; path = ../../soundlib/Mixer.h; sourceTree = "<group>"; };
+		22594181666D12B35A3E97C1 /* fileref.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = fileref.hpp; path = ../../src/mpt/io_file/fileref.hpp; sourceTree = "<group>"; };
 		2323E4A5944685974A0D32E5 /* Snd_defs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Snd_defs.h; path = ../../soundlib/Snd_defs.h; sourceTree = "<group>"; };
 		236E8DFB1D304A6D572F4C3B /* Tagging.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Tagging.h; path = ../../soundlib/Tagging.h; sourceTree = "<group>"; };
 		23EF16CF7F4F3B81C192DD0F /* filedata_base_buffered.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = filedata_base_buffered.hpp; path = ../../src/mpt/io_read/filedata_base_buffered.hpp; sourceTree = "<group>"; };
@@ -278,6 +279,7 @@
 		3286C74DF54727BF527C058D /* Flanger.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Flanger.cpp; path = ../../soundlib/plugins/dmo/Flanger.cpp; sourceTree = "<group>"; };
 		32BAAF6B89D057DDB618EDAB /* tests_base_saturate_cast.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = tests_base_saturate_cast.hpp; path = ../../src/mpt/base/tests/tests_base_saturate_cast.hpp; sourceTree = "<group>"; };
 		338C775D77DB1CCFBA02559D /* BitReader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BitReader.h; path = ../../soundlib/BitReader.h; sourceTree = "<group>"; };
+		34AF58C934523BFB6EA6EF09 /* fileadapter.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = fileadapter.hpp; path = ../../src/mpt/io_file/fileadapter.hpp; sourceTree = "<group>"; };
 		34D45985C7622377303AC7C5 /* Load_imf.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_imf.cpp; path = ../../soundlib/Load_imf.cpp; sourceTree = "<group>"; };
 		352908E1F7E96953551E4721 /* libopenmpt_c.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = libopenmpt_c.cpp; path = ../../libopenmpt/libopenmpt_c.cpp; sourceTree = "<group>"; };
 		36CAA59BC9586F8D323113DB /* Load_dmf.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_dmf.cpp; path = ../../soundlib/Load_dmf.cpp; sourceTree = "<group>"; };
@@ -285,6 +287,7 @@
 		37F80675CA85D067335E74B5 /* Load_amf.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_amf.cpp; path = ../../soundlib/Load_amf.cpp; sourceTree = "<group>"; };
 		38019939FAC1F9AB57F6D779 /* ContainerPP20.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ContainerPP20.cpp; path = ../../soundlib/ContainerPP20.cpp; sourceTree = "<group>"; };
 		392551FD7D73F76FBF9B303D /* ModSample.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ModSample.h; path = ../../soundlib/ModSample.h; sourceTree = "<group>"; };
+		394347E11EDA4F13E21F5E21 /* unique_tempfilename.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = unique_tempfilename.hpp; path = ../../src/mpt/io_file/unique_tempfilename.hpp; sourceTree = "<group>"; };
 		3947FF534AFF3F45C06D2D93 /* AudioCriticalSection.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = AudioCriticalSection.cpp; path = ../../soundlib/AudioCriticalSection.cpp; sourceTree = "<group>"; };
 		39C1318B738BE83D28E737CB /* filecursor_filename_traits.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = filecursor_filename_traits.hpp; path = ../../src/mpt/io_read/filecursor_filename_traits.hpp; sourceTree = "<group>"; };
 		39CF376FCC5D01613535A5AF /* FloatMixer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = FloatMixer.h; path = ../../soundlib/FloatMixer.h; sourceTree = "<group>"; };
@@ -346,6 +349,7 @@
 		597941C14D788B33D263A001 /* libopenmpt_cxx.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = libopenmpt_cxx.cpp; path = ../../libopenmpt/libopenmpt_cxx.cpp; sourceTree = "<group>"; };
 		5AF83E8DC8204CFFC9B33CCD /* pointer.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = pointer.hpp; path = ../../src/mpt/base/pointer.hpp; sourceTree = "<group>"; };
 		5BE26A37EE7034295748D877 /* modcommand.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = modcommand.h; path = ../../soundlib/modcommand.h; sourceTree = "<group>"; };
+		5C38D0E103AE6C93AC737721 /* inputfile_filecursor.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = inputfile_filecursor.hpp; path = ../../src/mpt/io_file/inputfile_filecursor.hpp; sourceTree = "<group>"; };
 		5CDA3BEFB6DB52E17E224A2F /* SampleFormatVorbis.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = SampleFormatVorbis.cpp; path = ../../soundlib/SampleFormatVorbis.cpp; sourceTree = "<group>"; };
 		5CFFC0612B231413B23166A1 /* parse.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = parse.hpp; path = ../../src/mpt/parse/parse.hpp; sourceTree = "<group>"; };
 		5D6A74BCEFF83EAE58D0E2FC /* mptRandom.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = mptRandom.cpp; path = ../../common/mptRandom.cpp; sourceTree = "<group>"; };
@@ -425,6 +429,7 @@
 		8B1AB3CB32904F7DDB555A0B /* filecursor_stdstream.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = filecursor_stdstream.hpp; path = ../../src/mpt/io_read/filecursor_stdstream.hpp; sourceTree = "<group>"; };
 		8BE3D4DDCE50214F8676931D /* libopenmpt_stream_callbacks_file_posix.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = libopenmpt_stream_callbacks_file_posix.h; path = ../../libopenmpt/libopenmpt_stream_callbacks_file_posix.h; sourceTree = "<group>"; };
 		8BE9A4117CE11203E8E49251 /* patternContainer.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = patternContainer.cpp; path = ../../soundlib/patternContainer.cpp; sourceTree = "<group>"; };
+		8BFBDA8B261FCFBDD645B0CB /* unique_basename.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = unique_basename.hpp; path = ../../src/mpt/io_file/unique_basename.hpp; sourceTree = "<group>"; };
 		8C3D549D4EFDB50FAC3292DD /* ParamEq.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ParamEq.cpp; path = ../../soundlib/plugins/dmo/ParamEq.cpp; sourceTree = "<group>"; };
 		8C8CA6B3531A9E25C11CC4F3 /* ModSampleCopy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ModSampleCopy.h; path = ../../soundlib/ModSampleCopy.h; sourceTree = "<group>"; };
 		8CA9A5C72521933951088407 /* simple_floatingpoint.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = simple_floatingpoint.hpp; path = ../../src/mpt/format/simple_floatingpoint.hpp; sourceTree = "<group>"; };
@@ -459,6 +464,7 @@
 		9E737DF712CDC2E990B14C37 /* Distortion.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Distortion.h; path = ../../soundlib/plugins/dmo/Distortion.h; sourceTree = "<group>"; };
 		9E8AC865F88BDF57BFD2D6A5 /* tests_binary.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = tests_binary.hpp; path = ../../src/mpt/binary/tests/tests_binary.hpp; sourceTree = "<group>"; };
 		9EB0D073130B156590EE9EB3 /* Compressor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Compressor.h; path = ../../soundlib/plugins/dmo/Compressor.h; sourceTree = "<group>"; };
+		9F187E69B9E4ED1BBDB584A9 /* outputfile.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = outputfile.hpp; path = ../../src/mpt/io_file/outputfile.hpp; sourceTree = "<group>"; };
 		9FCB791A628BD98CBFC0B75A /* dos_memory.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = dos_memory.hpp; path = ../../src/mpt/osinfo/dos_memory.hpp; sourceTree = "<group>"; };
 		A0ADCD61E4FC72D32723ABA1 /* MPEGFrame.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MPEGFrame.h; path = ../../soundlib/MPEGFrame.h; sourceTree = "<group>"; };
 		A105879DAD903A0F287505DD /* source_location.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = source_location.hpp; path = ../../src/mpt/base/source_location.hpp; sourceTree = "<group>"; };
@@ -519,6 +525,7 @@
 		CB4660EB0F95065D51BC3F2B /* pattern.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = pattern.cpp; path = ../../soundlib/pattern.cpp; sourceTree = "<group>"; };
 		CB69F5BB3FC43AADBDA7C3FB /* patternContainer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = patternContainer.h; path = ../../soundlib/patternContainer.h; sourceTree = "<group>"; };
 		CC1927E18ED98853EC0E6621 /* ModInstrument.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ModInstrument.cpp; path = ../../soundlib/ModInstrument.cpp; sourceTree = "<group>"; };
+		CE0AB78B121E88BD05F00DCB /* fstream.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = fstream.hpp; path = ../../src/mpt/io_file/fstream.hpp; sourceTree = "<group>"; };
 		CE271C83BB8EAF355888A2C3 /* filedata_stdstream.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = filedata_stdstream.hpp; path = ../../src/mpt/io_read/filedata_stdstream.hpp; sourceTree = "<group>"; };
 		CE32D5B4DABD882655A253F4 /* PluginManager.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = PluginManager.cpp; path = ../../soundlib/plugins/PluginManager.cpp; sourceTree = "<group>"; };
 		CE6385639C86D91523952BA3 /* split.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = split.hpp; path = ../../src/mpt/parse/split.hpp; sourceTree = "<group>"; };
@@ -585,6 +592,7 @@
 		FB635E3D352E14EFEA89647D /* filecursor_traits_filedata.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = filecursor_traits_filedata.hpp; path = ../../src/mpt/io_read/filecursor_traits_filedata.hpp; sourceTree = "<group>"; };
 		FBE197BEECD905B058DC85FE /* serialization_utils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = serialization_utils.h; path = ../../common/serialization_utils.h; sourceTree = "<group>"; };
 		FBF22E79CF82B5EBC2544CB9 /* libopenmpt_stream_callbacks_file_posix_lfs64.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = libopenmpt_stream_callbacks_file_posix_lfs64.h; path = ../../libopenmpt/libopenmpt_stream_callbacks_file_posix_lfs64.h; sourceTree = "<group>"; };
+		FC85D407DBA2EE39B7AC4A47 /* inputfile.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = inputfile.hpp; path = ../../src/mpt/io_file/inputfile.hpp; sourceTree = "<group>"; };
 		FED01DA32FB4159542CC4BE3 /* tuning.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = tuning.h; path = ../../soundlib/tuning.h; sourceTree = "<group>"; };
 		FEDB3A1791690409FA41A857 /* Echo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Echo.h; path = ../../soundlib/plugins/dmo/Echo.h; sourceTree = "<group>"; };
 		FF7F8F60F37ED8D27869EDA0 /* mptFileTemporary.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = mptFileTemporary.cpp; path = ../../common/mptFileTemporary.cpp; sourceTree = "<group>"; };
@@ -708,6 +716,21 @@
 			name = plugins;
 			sourceTree = "<group>";
 		};
+		3C7CE88D62A3183FC9AA0ECD /* io_file */ = {
+			isa = PBXGroup;
+			children = (
+				34AF58C934523BFB6EA6EF09 /* fileadapter.hpp */,
+				22594181666D12B35A3E97C1 /* fileref.hpp */,
+				CE0AB78B121E88BD05F00DCB /* fstream.hpp */,
+				FC85D407DBA2EE39B7AC4A47 /* inputfile.hpp */,
+				5C38D0E103AE6C93AC737721 /* inputfile_filecursor.hpp */,
+				9F187E69B9E4ED1BBDB584A9 /* outputfile.hpp */,
+				8BFBDA8B261FCFBDD645B0CB /* unique_basename.hpp */,
+				394347E11EDA4F13E21F5E21 /* unique_tempfilename.hpp */,
+			);
+			name = io_file;
+			sourceTree = "<group>";
+		};
 		42C3CDB59951382771CA4BF5 /* tests */ = {
 			isa = PBXGroup;
 			children = (
@@ -926,6 +949,7 @@
 				C2D2C30558498237C6E5D945 /* exception_text */,
 				4ECA2D3165DCC86355F8C371 /* format */,
 				1063AA8FB3AF33C1CE8000CF /* io */,
+				3C7CE88D62A3183FC9AA0ECD /* io_file */,
 				FB1C5B4521428AF788498185 /* io_read */,
 				54C514A39D35F8D5E1D4CAE3 /* io_write */,
 				8FB69E0532A4C4B7C1CAA445 /* mutex */,
Index: common/mptFileIO.cpp
===================================================================
--- common/mptFileIO.cpp	(revision 17577)
+++ common/mptFileIO.cpp	(working copy)
@@ -11,24 +11,6 @@
 #include "stdafx.h"
 #include "mptFileIO.h"
 
-#if defined(MPT_ENABLE_FILEIO)
-#include "mpt/base/detect_libcxx.hpp"
-#include "mpt/io/io.hpp"
-#include "mpt/io/io_stdstream.hpp"
-#if defined(MODPLUG_TRACKER) && MPT_OS_WINDOWS
-#include "mpt/system_error/system_error.hpp"
-#include "FileReader.h"
-#endif // MODPLUG_TRACKER && MPT_OS_WINDOWS
-#endif // MPT_ENABLE_FILEIO
-
-#if defined(MODPLUG_TRACKER) && MPT_OS_WINDOWS
-#include "mptFileTemporary.h"
-#endif // MODPLUG_TRACKER && MPT_OS_WINDOWS
-
-#if defined(MPT_ENABLE_FILEIO)
-#include <stdexcept>
-#endif // MPT_ENABLE_FILEIO
-
 #ifdef MODPLUG_TRACKER
 #if MPT_OS_WINDOWS
 #include <windows.h>
@@ -37,15 +19,7 @@
 #endif // MPT_OS_WINDOWS
 #endif // MODPLUG_TRACKER
 
-#if defined(MPT_ENABLE_FILEIO)
-#if MPT_LIBCXX_MS
-#include <stdio.h>
-#include <tchar.h>
-#endif // MPT_LIBCXX_MS
-#endif // MPT_ENABLE_FILEIO
 
-
-
 OPENMPT_NAMESPACE_BEGIN
 
 
@@ -52,7 +26,6 @@
 #if defined(MPT_ENABLE_FILEIO)
 
 
-
 #if !defined(MPT_BUILD_SILENCE_LIBOPENMPT_CONFIGURATION_WARNINGS)
 
 #if defined(MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR)
@@ -64,9 +37,10 @@
 #endif // !MPT_BUILD_SILENCE_LIBOPENMPT_CONFIGURATION_WARNINGS
 
 
+#ifdef MODPLUG_TRACKER
 
-#ifdef MODPLUG_TRACKER
 #if MPT_OS_WINDOWS
+
 bool SetFilesystemCompression(HANDLE hFile)
 {
 	if(hFile == INVALID_HANDLE_VALUE)
@@ -78,6 +52,7 @@
 	BOOL result = DeviceIoControl(hFile, FSCTL_SET_COMPRESSION, (LPVOID)&format, sizeof(format), NULL, 0, &dummy /*required*/ , NULL);
 	return result != FALSE;
 }
+
 bool SetFilesystemCompression(int fd)
 {
 	if(fd < 0)
@@ -92,6 +67,7 @@
 	}
 	return SetFilesystemCompression(hFile);
 }
+
 bool SetFilesystemCompression(const mpt::PathString &filename)
 {
 	DWORD attributes = GetFileAttributes(mpt::SupportLongPath(filename.AsNative()).c_str());
@@ -112,494 +88,12 @@
 	CloseHandle(hFile);
 	return result;
 }
+
 #endif // MPT_OS_WINDOWS
-#endif // MODPLUG_TRACKER
 
-
-
-#ifdef MODPLUG_TRACKER
-
-namespace mpt {
-
-#if MPT_LIBCXX_MS
-
-mpt::tstring SafeOutputFile::convert_mode(std::ios_base::openmode mode, FlushMode flushMode)
-{
-	mpt::tstring fopen_mode;
-	switch(mode & ~(std::ios_base::ate | std::ios_base::binary))
-	{
-	case std::ios_base::in:
-		fopen_mode = _T("r");
-		break;
-	case std::ios_base::out:
-		[[fallthrough]];
-	case std::ios_base::out | std::ios_base::trunc:
-		fopen_mode = _T("w");
-		break;
-	case std::ios_base::app:
-		[[fallthrough]];
-	case std::ios_base::out | std::ios_base::app:
-		fopen_mode = _T("a");
-		break;
-	case std::ios_base::out | std::ios_base::in:
-		fopen_mode = _T("r+");
-		break;
-	case std::ios_base::out | std::ios_base::in | std::ios_base::trunc:
-		fopen_mode = _T("w+");
-		break;
-	case std::ios_base::out | std::ios_base::in | std::ios_base::app:
-		[[fallthrough]];
-	case std::ios_base::in | std::ios_base::app:
-		fopen_mode = _T("a+");
-		break;
-	}
-	if(fopen_mode.empty())
-	{
-		return fopen_mode;
-	}
-	if(mode & std::ios_base::binary)
-	{
-		fopen_mode += _T("b");
-	}
-	if(flushMode == FlushMode::Full)
-	{
-		fopen_mode += _T("c");  // force commit on fflush (MSVC specific)
-	}
-	return fopen_mode;
-}
-
-std::FILE * SafeOutputFile::internal_fopen(const mpt::PathString &filename, std::ios_base::openmode mode, FlushMode flushMode)
-{
-	m_f = nullptr;
-	mpt::tstring fopen_mode = convert_mode(mode, flushMode);
-	if(fopen_mode.empty())
-	{
-		return nullptr;
-	}
-	std::FILE *f =
-		#ifdef UNICODE
-			_wfopen(mpt::SupportLongPath(filename.AsNative()).c_str(), fopen_mode.c_str())
-		#else
-			std::fopen(mpt::SupportLongPath(filename.AsNative()).c_str(), fopen_mode.c_str())
-		#endif
-		;
-	if(!f)
-	{
-		return nullptr;
-	}
-	if(mode & std::ios_base::ate)
-	{
-		if(std::fseek(f, 0, SEEK_END) != 0)
-		{
-			std::fclose(f);
-			f = nullptr;
-			return nullptr;
-		}
-	}
-	m_f = f;
-	return f;
-}
-
-#endif // MPT_LIBCXX_MS
-
-// cppcheck-suppress exceptThrowInDestructor
-SafeOutputFile::~SafeOutputFile() noexcept(false)
-{
-	const bool mayThrow = (std::uncaught_exceptions() == 0);
-	if(!stream())
-	{
-		#if MPT_LIBCXX_MS
-			if(m_f)
-			{
-				std::fclose(m_f);
-			}
-		#endif // MPT_LIBCXX_MS
-		if(mayThrow && (stream().exceptions() & (std::ios::badbit | std::ios::failbit)))
-		{
-			// cppcheck-suppress exceptThrowInDestructor
-			throw std::ios_base::failure(std::string("Error before flushing file buffers."));
-		}
-		return;
-	}
-	if(!stream().rdbuf())
-	{
-		#if MPT_LIBCXX_MS
-			if(m_f)
-			{
-				std::fclose(m_f);
-			}
-		#endif // MPT_LIBCXX_MS
-		if(mayThrow && (stream().exceptions() & (std::ios::badbit | std::ios::failbit)))
-		{
-			// cppcheck-suppress exceptThrowInDestructor
-			throw std::ios_base::failure(std::string("Error before flushing file buffers."));
-		}
-		return;
-	}
-#if MPT_LIBCXX_MS
-	if(!m_f)
-	{
-		return;
-	}
-#endif // MPT_LIBCXX_MS
-	bool errorOnFlush = false;
-	if(m_FlushMode != FlushMode::None)
-	{
-		try
-		{
-			if(stream().rdbuf()->pubsync() != 0)
-			{
-				errorOnFlush = true;
-			}
-		} catch(const std::exception &)
-		{
-			errorOnFlush = true;
-#if MPT_LIBCXX_MS
-			if(m_FlushMode != FlushMode::None)
-			{
-				if(std::fflush(m_f) != 0)
-				{
-					errorOnFlush = true;
-				}
-			}
-			if(std::fclose(m_f) != 0)
-			{
-				errorOnFlush = true;
-			}
-#endif // MPT_LIBCXX_MS
-			if(mayThrow)
-			{
-				// ignore errorOnFlush here, and re-throw the earlier exception
-				// cppcheck-suppress exceptThrowInDestructor
-				throw;
-			}
-		}
-	}
-#if MPT_LIBCXX_MS
-	if(m_FlushMode != FlushMode::None)
-	{
-		if(std::fflush(m_f) != 0)
-		{
-			errorOnFlush = true;
-		}
-	}
-	if(std::fclose(m_f) != 0)
-	{
-		errorOnFlush = true;
-	}
-#endif // MPT_LIBCXX_MS
-	if(mayThrow && errorOnFlush && (stream().exceptions() & (std::ios::badbit | std::ios::failbit)))
-	{
-		// cppcheck-suppress exceptThrowInDestructor
-		throw std::ios_base::failure(std::string("Error flushing file buffers."));
-	}
-}
-
-} // namespace mpt
-
 #endif // MODPLUG_TRACKER
 
 
-
-#ifdef MODPLUG_TRACKER
-
-namespace mpt {
-
-LazyFileRef & LazyFileRef::operator = (const std::vector<std::byte> &data)
-{
-	mpt::ofstream file(m_Filename, std::ios::binary);
-	file.exceptions(std::ios_base::failbit | std::ios_base::badbit);
-	mpt::IO::WriteRaw(file, mpt::as_span(data));
-	mpt::IO::Flush(file);
-	return *this;
-}
-
-LazyFileRef & LazyFileRef::operator = (const std::vector<char> &data)
-{
-	mpt::ofstream file(m_Filename, std::ios::binary);
-	file.exceptions(std::ios_base::failbit | std::ios_base::badbit);
-	mpt::IO::WriteRaw(file, mpt::as_span(data));
-	mpt::IO::Flush(file);
-	return *this;
-}
-
-LazyFileRef & LazyFileRef::operator = (const std::string &data)
-{
-	mpt::ofstream file(m_Filename, std::ios::binary);
-	file.exceptions(std::ios_base::failbit | std::ios_base::badbit);
-	mpt::IO::WriteRaw(file, mpt::as_span(data));
-	mpt::IO::Flush(file);
-	return *this;
-}
-
-LazyFileRef::operator std::vector<std::byte> () const
-{
-	mpt::ifstream file(m_Filename, std::ios::binary);
-	if(!mpt::IO::IsValid(file))
-	{
-		return std::vector<std::byte>();
-	}
-	file.exceptions(std::ios_base::failbit | std::ios_base::badbit);
-	mpt::IO::SeekEnd(file);
-	std::vector<std::byte> buf(mpt::saturate_cast<std::size_t>(mpt::IO::TellRead(file)));
-	mpt::IO::SeekBegin(file);
-	mpt::IO::ReadRaw(file, mpt::as_span(buf));
-	return buf;
-}
-
-LazyFileRef::operator std::vector<char> () const
-{
-	mpt::ifstream file(m_Filename, std::ios::binary);
-	if(!mpt::IO::IsValid(file))
-	{
-		return std::vector<char>();
-	}
-	file.exceptions(std::ios_base::failbit | std::ios_base::badbit);
-	mpt::IO::SeekEnd(file);
-	std::vector<char> buf(mpt::saturate_cast<std::size_t>(mpt::IO::TellRead(file)));
-	mpt::IO::SeekBegin(file);
-	mpt::IO::ReadRaw(file, mpt::as_span(buf));
-	return buf;
-}
-
-LazyFileRef::operator std::string () const
-{
-	mpt::ifstream file(m_Filename, std::ios::binary);
-	if(!mpt::IO::IsValid(file))
-	{
-		return std::string();
-	}
-	file.exceptions(std::ios_base::failbit | std::ios_base::badbit);
-	mpt::IO::SeekEnd(file);
-	std::vector<char> buf(mpt::saturate_cast<std::size_t>(mpt::IO::TellRead(file)));
-	mpt::IO::SeekBegin(file);
-	mpt::IO::ReadRaw(file, mpt::as_span(buf));
-	return mpt::buffer_cast<std::string>(buf);
-}
-
-} // namespace mpt
-
-#endif // MODPLUG_TRACKER
-
-
-InputFile::InputFile(const mpt::PathString &filename, bool allowWholeFileCaching)
-	: m_Filename(filename)
-	, m_File(filename, std::ios::binary | std::ios::in)
-	, m_IsValid(false)
-	, m_IsCached(false)
-{
-	MPT_ASSERT(!filename.empty());
-	if(allowWholeFileCaching)
-	{
-		if(mpt::IO::IsReadSeekable(m_File))
-		{
-			if(!mpt::IO::SeekEnd(m_File))
-			{
-				m_File.close();
-				return;
-			}
-			mpt::IO::Offset filesize = mpt::IO::TellRead(m_File);
-			if(!mpt::IO::SeekBegin(m_File))
-			{
-				m_File.close();
-				return;
-			}
-			if(mpt::in_range<std::size_t>(filesize))
-			{
-				std::size_t buffersize = mpt::saturate_cast<std::size_t>(filesize);
-				m_Cache.resize(buffersize);
-				if(mpt::IO::ReadRaw(m_File, mpt::as_span(m_Cache)).size() != mpt::saturate_cast<std::size_t>(filesize))
-				{
-					m_File.close();
-					return;
-				}
-				if(!mpt::IO::SeekBegin(m_File))
-				{
-					m_File.close();
-					return;
-				}
-				m_IsCached = true;
-				m_IsValid = true;
-				return;
-			}
-		}
-	}
-	m_IsValid = true;
-	return;
-}
-
-
-InputFile::~InputFile()
-{
-	return;
-}
-
-
-
-bool InputFile::IsValid() const
-{
-	return m_IsValid && m_File.good();
-}
-
-
-bool InputFile::IsCached() const
-{
-	return m_IsCached;
-}
-
-
-mpt::PathString InputFile::GetFilename() const
-{
-	return m_Filename;
-}
-
-
-std::istream& InputFile::GetStream()
-{
-	MPT_ASSERT(!m_IsCached);
-	return m_File;
-}
-
-
-mpt::const_byte_span InputFile::GetCache()
-{
-	MPT_ASSERT(m_IsCached);
-	return mpt::as_span(m_Cache);
-}
-
-
-
-#if defined(MODPLUG_TRACKER) && MPT_OS_WINDOWS
-
-
-OnDiskFileWrapper::OnDiskFileWrapper(FileCursor &file, const mpt::PathString &fileNameExtension)
-	: m_IsTempFile(false)
-{
-	try
-	{
-		file.Rewind();
-		if(!file.GetOptionalFileName())
-		{
-			const mpt::PathString tempName = mpt::TemporaryPathname{P_("OpenMPT"), fileNameExtension}.GetPathname();
-
-#if MPT_OS_WINDOWS && MPT_OS_WINDOWS_WINRT
-#if (_WIN32_WINNT < 0x0602)
-#define MPT_ONDISKFILEWRAPPER_NO_CREATEFILE
-#endif
-#endif
-
-#ifdef MPT_ONDISKFILEWRAPPER_NO_CREATEFILE
-
-			mpt::ofstream f(tempName, std::ios::binary);
-			if(!f)
-			{
-				throw std::runtime_error("Error creating temporary file.");
-			}
-			while(!file.EndOfFile())
-			{
-				FileCursor::PinnedView view = file.ReadPinnedView(mpt::IO::BUFFERSIZE_NORMAL);
-				std::size_t towrite = view.size();
-				std::size_t written = 0;
-				do
-				{
-					std::size_t chunkSize = mpt::saturate_cast<std::size_t>(towrite);
-					bool chunkOk = false;
-					chunkOk = mpt::IO::WriteRaw(f, mpt::const_byte_span(view.data() + written, chunkSize));
-					if(!chunkOk)
-					{
-						throw std::runtime_error("Incomplete Write.");
-					}
-					towrite -= chunkSize;
-					written += chunkSize;
-				} while(towrite > 0);
-			}
-			f.close();
-
-#else // !MPT_ONDISKFILEWRAPPER_NO_CREATEFILE
-
-			HANDLE hFile = NULL;
-			#if MPT_OS_WINDOWS_WINRT
-				hFile = mpt::windows::CheckFileHANDLE(CreateFile2(tempName.AsNative().c_str(), GENERIC_WRITE, FILE_SHARE_READ, CREATE_ALWAYS, NULL));
-			#else
-				hFile = mpt::windows::CheckFileHANDLE(CreateFile(tempName.AsNative().c_str(), GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL));
-			#endif
-			while(!file.EndOfFile())
-			{
-				FileCursor::PinnedView view = file.ReadPinnedView(mpt::IO::BUFFERSIZE_NORMAL);
-				std::size_t towrite = view.size();
-				std::size_t written = 0;
-				do
-				{
-					DWORD chunkSize = mpt::saturate_cast<DWORD>(towrite);
-					DWORD chunkDone = 0;
-					try
-					{
-						mpt::windows::CheckBOOL(WriteFile(hFile, view.data() + written, chunkSize, &chunkDone, NULL));
-					} catch(...)
-					{
-						CloseHandle(hFile);
-						hFile = NULL;
-						throw;
-					}
-					if(chunkDone != chunkSize)
-					{
-						CloseHandle(hFile);
-						hFile = NULL;
-						throw std::runtime_error("Incomplete WriteFile().");
-					}
-					towrite -= chunkDone;
-					written += chunkDone;
-				} while(towrite > 0);
-			}
-			CloseHandle(hFile);
-			hFile = NULL;
-
-#endif // MPT_ONDISKFILEWRAPPER_NO_CREATEFILE
-
-			m_Filename = tempName;
-			m_IsTempFile = true;
-		} else
-		{
-			m_Filename = file.GetOptionalFileName().value();
-		}
-	} catch (const std::runtime_error &)
-	{
-		m_IsTempFile = false;
-		m_Filename = mpt::PathString();
-	}
-}
-
-
-OnDiskFileWrapper::~OnDiskFileWrapper()
-{
-	if(m_IsTempFile)
-	{
-		DeleteFile(m_Filename.AsNative().c_str());
-		m_IsTempFile = false;
-	}
-	m_Filename = mpt::PathString();
-}
-
-
-bool OnDiskFileWrapper::IsValid() const
-{
-	return !m_Filename.empty();
-}
-
-
-mpt::PathString OnDiskFileWrapper::GetFilename() const
-{
-	return m_Filename;
-}
-
-
-#endif // MODPLUG_TRACKER && MPT_OS_WINDOWS
-
-
-#else // !MPT_ENABLE_FILEIO
-
-MPT_MSVC_WORKAROUND_LNK4221(mptFileIO)
-
 #endif // MPT_ENABLE_FILEIO
 
 
Index: common/mptFileIO.h
===================================================================
--- common/mptFileIO.h	(revision 17577)
+++ common/mptFileIO.h	(working copy)
@@ -14,30 +14,16 @@
 #if defined(MPT_ENABLE_FILEIO)
 
 #include "mpt/base/detect_libcxx.hpp"
-#include "mpt/io_read/filecursor_memory.hpp"
-#include "mpt/io_read/filecursor_stdstream.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/io_file/fstream.hpp"
+#include "mpt/io_file/inputfile_filecursor.hpp"
 
 #include "../common/mptString.h"
 #include "../common/mptPathString.h"
 #include "../common/FileReaderFwd.h"
 
-#if defined(MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR)
-#if MPT_GCC_AT_LEAST(9,1,0)
-#include <filesystem>
-#endif // MPT_GCC_AT_LEAST(9,1,0)
-#endif // MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR
-#include <fstream>
-#include <ios>
-#include <ostream>
-#include <streambuf>
-#include <string>
-#include <string_view>
 #include <utility>
 
-#if MPT_LIBCXX_MS
-#include <cstdio>
-#endif // !MPT_LIBCXX_MS
-
 #ifdef MODPLUG_TRACKER
 #if MPT_OS_WINDOWS
 #include <windows.h>
@@ -69,320 +55,27 @@
 namespace mpt
 {
 
-namespace detail
-{
+using fstream = mpt::IO::fstream;
+using ifstream = mpt::IO::ifstream;
+using ofstream = mpt::IO::ofstream;
 
-template<typename Tbase>
-inline void fstream_open(Tbase & base, const mpt::PathString & filename, std::ios_base::openmode mode)
-{
-	#if defined(MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR)
-		#if MPT_GCC_AT_LEAST(9,1,0)
-			base.open(static_cast<std::filesystem::path>(filename.AsNative()), mode);
-		#else // !MPT_GCC_AT_LEAST(9,1,0)
-			// Warning: MinGW with GCC earlier than 9.1 detected. Standard library does neither provide std::fstream wchar_t overloads nor std::filesystem with wchar_t support. Unicode filename support is thus unavailable.
-			base.open(mpt::ToCharset(mpt::Charset::Locale, filename.AsNative()).c_str(), mode);
-		#endif // MPT_GCC_AT_LEAST(9,1,0)
-	#else // !MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR
-		base.open(mpt::SupportLongPath(filename.AsNative()).c_str(), mode);
-	#endif // MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR
-}
-
-} // namespace detail
-
-// We cannot rely on implicit conversion of mpt::PathString to std::filesystem::path when constructing std::fstream
-// because of broken overload implementation in GCC libstdc++ 8, 9, 10.
-// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95642
-// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90704
-
-class fstream
-	: public std::fstream
-{
-private:
-	typedef std::fstream Tbase;
-public:
-	fstream(const mpt::PathString & filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out)
-	{
-		detail::fstream_open<Tbase>(*this, filename, mode);
-	}
-#if MPT_LIBCXX_MS
-protected:
-	fstream(std::FILE * file)
-		: std::fstream(file)
-	{
-	}
-#endif // MPT_LIBCXX_MS
-public:
-	void open(const char * filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) = delete;
-	void open(const std::string_view & filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) = delete;
-	void open(const std::string & filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) = delete;
-#if MPT_OS_WINDOWS && !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
-	void open(const wchar_t * filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) = delete;
-	void open(const std::wstring_view & filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) = delete;
-	void open(const std::wstring & filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) = delete;
-#endif
-};
-
-class ifstream
-	: public std::ifstream
-{
-private:
-	typedef std::ifstream Tbase;
-public:
-	ifstream(const mpt::PathString & filename, std::ios_base::openmode mode = std::ios_base::in)
-	{
-		detail::fstream_open<Tbase>(*this, filename, mode);
-	}
-#if MPT_LIBCXX_MS
-protected:
-	ifstream(std::FILE * file)
-		: std::ifstream(file)
-	{
-	}
-#endif // MPT_LIBCXX_MS
-public:
-	void open(const char * filename, std::ios_base::openmode mode = std::ios_base::in) = delete;
-	void open(const std::string_view & filename, std::ios_base::openmode mode = std::ios_base::in) = delete;
-	void open(const std::string & filename, std::ios_base::openmode mode = std::ios_base::in) = delete;
-#if MPT_OS_WINDOWS && !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
-	void open(const wchar_t * filename, std::ios_base::openmode mode = std::ios_base::in) = delete;
-	void open(const std::wstring_view & filename, std::ios_base::openmode mode = std::ios_base::in) = delete;
-	void open(const std::wstring & filename, std::ios_base::openmode mode = std::ios_base::in) = delete;
-#endif
-};
-
-class ofstream
-	: public std::ofstream
-{
-private:
-	typedef std::ofstream Tbase;
-public:
-	ofstream(const mpt::PathString & filename, std::ios_base::openmode mode = std::ios_base::out)
-	{
-		detail::fstream_open<Tbase>(*this, filename, mode);
-	}
-#if MPT_LIBCXX_MS
-protected:
-	ofstream(std::FILE * file)
-		: std::ofstream(file)
-	{
-	}
-#endif // MPT_LIBCXX_MS
-public:
-	void open(const char * filename, std::ios_base::openmode mode = std::ios_base::out) = delete;
-	void open(const std::string_view & filename, std::ios_base::openmode mode = std::ios_base::out) = delete;
-	void open(const std::string & filename, std::ios_base::openmode mode = std::ios_base::out) = delete;
-#if MPT_OS_WINDOWS && !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
-	void open(const wchar_t * filename, std::ios_base::openmode mode = std::ios_base::out) = delete;
-	void open(const std::wstring_view & filename, std::ios_base::openmode mode = std::ios_base::out) = delete;
-	void open(const std::wstring & filename, std::ios_base::openmode mode = std::ios_base::out) = delete;
-#endif
-};
-
-enum class FlushMode
-{
-	None   = 0,  // no explicit flushes at all
-	Single = 1,  // explicitly flush higher-leverl API layers
-	Full   = 2,  // explicitly flush *all* layers, up to and including disk write caches
-};
-
-inline FlushMode FlushModeFromBool(bool flush)
-{
-	return flush ? FlushMode::Full : FlushMode::None;
-}
-
-#ifdef MODPLUG_TRACKER
-
-class SafeOutputFile
-{
-private:
-	FlushMode m_FlushMode;
-#if MPT_LIBCXX_MS
-	std::FILE *m_f = nullptr;
-#else // !MPT_LIBCXX_MS
-	mpt::ofstream m_s;
-#endif // MPT_LIBCXX_MS
-#if MPT_LIBCXX_MS
-	class FILEostream
-		: public mpt::ofstream
-	{
-	public:
-		FILEostream(std::FILE * file)
-			: mpt::ofstream(file)
-		{
-			return;
-		}
-	};
-	FILEostream m_s;
-	static mpt::tstring convert_mode(std::ios_base::openmode mode, FlushMode flushMode);
-	std::FILE * internal_fopen(const mpt::PathString &filename, std::ios_base::openmode mode, FlushMode flushMode);
-#endif // MPT_LIBCXX_MS
-public:
-	SafeOutputFile() = delete;
-	explicit SafeOutputFile(const mpt::PathString &filename, std::ios_base::openmode mode = std::ios_base::out, FlushMode flushMode = FlushMode::Full)
-		: m_FlushMode(flushMode)
-#if MPT_LIBCXX_MS
-		, m_s(internal_fopen(filename, mode | std::ios_base::out, flushMode))
-#else // !MPT_LIBCXX_MS
-		, m_s(filename, mode)
-#endif // MPT_LIBCXX_MS
-	{
-		if(!stream().is_open())
-		{
-			stream().setstate(mpt::ofstream::failbit);
-		}
-	}
-	mpt::ofstream& stream()
-	{
-		return m_s;
-	}
-	operator mpt::ofstream& ()
-	{
-		return stream();
-	}
-	const mpt::ofstream& stream() const
-	{
-		return m_s;
-	}
-	operator const mpt::ofstream& () const
-	{
-		return stream();
-	}
-	operator bool() const
-	{
-		return stream() ? true : false;
-	}
-	bool operator!() const
-	{
-		return stream().operator!();
-	}
-	~SafeOutputFile() noexcept(false);
-};
-
-#endif // MODPLUG_TRACKER
-
-
-
-#ifdef MODPLUG_TRACKER
-
-// LazyFileRef is a simple reference to an on-disk file by the means of a
-// filename which allows easy assignment of the whole file contents to and from
-// byte buffers.
-class LazyFileRef {
-private:
-	const mpt::PathString m_Filename;
-public:
-	LazyFileRef(const mpt::PathString &filename)
-		: m_Filename(filename)
-	{
-		return;
-	}
-public:
-	LazyFileRef & operator = (const std::vector<std::byte> &data);
-	LazyFileRef & operator = (const std::vector<char> &data);
-	LazyFileRef & operator = (const std::string &data);
-	operator std::vector<std::byte> () const;
-	operator std::vector<char> () const;
-	operator std::string () const;
-};
-
-#endif // MODPLUG_TRACKER
-
-
 } // namespace mpt
 
 
-class InputFile
-{
-private:
-	mpt::PathString m_Filename;
-	mpt::ifstream m_File;
-	bool m_IsValid;
-	bool m_IsCached;
-	std::vector<std::byte> m_Cache;
-public:
-	InputFile(const mpt::PathString &filename, bool allowWholeFileCaching = false);
-	~InputFile();
-	bool IsValid() const;
-	bool IsCached() const;
-	mpt::PathString GetFilename() const;
-	std::istream& GetStream();
-	mpt::const_byte_span GetCache();
-};
-
-
 template <typename Targ1>
-inline FileCursor make_FileCursor(Targ1 &&arg1)
+inline FileCursor GetFileReader(Targ1 &&arg1)
 {
 	return mpt::IO::make_FileCursor<mpt::PathString>(std::forward<Targ1>(arg1));
 }
 
-template <typename Targ1, typename Targ2>
-inline FileCursor make_FileCursor(Targ1 &&arg1, Targ2 &&arg2)
-{
-	return mpt::IO::make_FileCursor<mpt::PathString>(std::forward<Targ1>(arg1), std::forward<Targ2>(arg2));
-}
 
-
-// templated in order to reduce header inter-dependencies
-class InputFile;
-template <typename TInputFile, std::enable_if_t<std::is_same<TInputFile, InputFile>::value, bool> = true>
-inline FileCursor make_FileCursor(TInputFile &file)
-{
-	if(!file.IsValid())
-	{
-		return FileCursor();
-	}
-	if(file.IsCached())
-	{
-		return mpt::IO::make_FileCursor<mpt::PathString>(file.GetCache(), std::make_shared<mpt::PathString>(file.GetFilename()));
-	} else
-	{
-		return mpt::IO::make_FileCursor<mpt::PathString>(file.GetStream(), std::make_shared<mpt::PathString>(file.GetFilename()));
-	}
-}
-
-
-template <typename Targ1>
-inline FileCursor GetFileReader(Targ1 &&arg1)
-{
-	return make_FileCursor(std::forward<Targ1>(arg1));
-}
-
-
 template <typename Targ1, typename Targ2>
 inline FileCursor GetFileReader(Targ1 &&arg1, Targ2 &&arg2)
 {
-	return make_FileCursor(std::forward<Targ1>(arg1), std::forward<Targ2>(arg2));
+	return mpt::IO::make_FileCursor<mpt::PathString>(std::forward<Targ1>(arg1), std::forward<Targ2>(arg2));
 }
 
 
-#if defined(MODPLUG_TRACKER) && MPT_OS_WINDOWS
-
-class OnDiskFileWrapper
-{
-
-private:
-
-	mpt::PathString m_Filename;
-	bool m_IsTempFile;
-
-public:
-
-	OnDiskFileWrapper(FileCursor& file, const mpt::PathString& fileNameExtension = P_("tmp"));
-
-	~OnDiskFileWrapper();
-
-public:
-
-	bool IsValid() const;
-
-	mpt::PathString GetFilename() const;
-
-}; // class OnDiskFileWrapper
-
-#endif // MODPLUG_TRACKER && MPT_OS_WINDOWS
-
-
 #endif // MPT_ENABLE_FILEIO
 
 
Index: common/mptFileTemporary.cpp
===================================================================
--- common/mptFileTemporary.cpp	(revision 17577)
+++ common/mptFileTemporary.cpp	(working copy)
@@ -10,6 +10,10 @@
 #include "stdafx.h"
 #include "mptFileTemporary.h"
 
+#if defined(MODPLUG_TRACKER) && MPT_OS_WINDOWS
+#include "mpt/io_file/unique_basename.hpp"
+#include "mpt/io_file/unique_tempfilename.hpp"
+#endif // MODPLUG_TRACKER && MPT_OS_WINDOWS
 #include "mpt/string_transcode/transcode.hpp"
 #include "mpt/uuid/uuid.hpp"
 
@@ -37,11 +41,25 @@
 
 TemporaryPathname::TemporaryPathname(const mpt::PathString &fileNamePrefix, const mpt::PathString &fileNameExtension)
 {
-	mpt::PathString filename = mpt::GetTempDirectory();
-	filename += (!fileNamePrefix.empty() ? fileNamePrefix + P_("_") : mpt::PathString());
-	filename += mpt::PathString::FromUnicode(mpt::UUID::GenerateLocalUseOnly(mpt::global_prng()).ToUString());
-	filename += (!fileNameExtension.empty() ? P_(".") + fileNameExtension : mpt::PathString());
-	m_Path = filename;
+	if(fileNamePrefix.empty())
+	{
+		if(fileNameExtension.empty())
+		{
+			m_Path = mpt::PathString::FromNative(mpt::IO::unique_tempfilename{mpt::IO::unique_basename{mpt::UUID::GenerateLocalUseOnly(mpt::global_prng())}});
+		} else
+		{
+			m_Path = mpt::PathString::FromNative(mpt::IO::unique_tempfilename{mpt::IO::unique_basename{mpt::UUID::GenerateLocalUseOnly(mpt::global_prng())}, P_(".") + fileNameExtension});
+		}
+	} else
+	{
+		if(fileNameExtension.empty())
+		{
+			m_Path = mpt::PathString::FromNative(mpt::IO::unique_tempfilename{mpt::IO::unique_basename{fileNamePrefix + P_("_"), mpt::UUID::GenerateLocalUseOnly(mpt::global_prng())}});
+		} else
+		{
+			m_Path = mpt::PathString::FromNative(mpt::IO::unique_tempfilename{mpt::IO::unique_basename{fileNamePrefix + P_("_"), mpt::UUID::GenerateLocalUseOnly(mpt::global_prng())}, P_(".") + fileNameExtension});
+		}
+	}
 }
 
 
Index: installer/updatesigntool/updatesigntool.cpp
===================================================================
--- installer/updatesigntool/updatesigntool.cpp	(revision 17577)
+++ installer/updatesigntool/updatesigntool.cpp	(working copy)
@@ -9,6 +9,8 @@
 #include "mpt/io/base.hpp"
 #include "mpt/io/io.hpp"
 #include "mpt/io/io_stdstream.hpp"
+#include "mpt/io_file/fstream.hpp"
+#include "mpt/io_file/outputfile.hpp"
 #include "mpt/out_of_memory/out_of_memory.hpp"
 #include "mpt/string/types.hpp"
 #include "mpt/string_transcode/transcode.hpp"
@@ -107,8 +109,8 @@
 			mpt::ustring filename = args[3];
 			mpt::crypto::keystore keystore(mpt::crypto::keystore::domain::user);
 			mpt::crypto::asymmetric::rsassa_pss<>::managed_private_key key(keystore, keyname);
-			mpt::SafeOutputFile sfo(mpt::PathString::FromUnicode(filename));
-			mpt::ofstream & fo = sfo.stream();
+			mpt::IO::SafeOutputFile sfo(mpt::PathString::FromUnicode(filename));
+			mpt::IO::ofstream & fo = sfo.stream();
 			mpt::IO::WriteText(fo, mpt::transcode<std::string>(mpt::common_encoding::utf8, key.get_public_key_data().as_jwk()));
 			fo.flush();
 		} else if(args[1] == MPT_USTRING("sign"))
@@ -125,7 +127,7 @@
 			mpt::crypto::asymmetric::rsassa_pss<>::managed_private_key key(keystore, keyname);
 			std::vector<std::byte> data;
 			{
-				mpt::ifstream fi(mpt::PathString::FromUnicode(inputfilename), std::ios::binary);
+				mpt::IO::ifstream fi(mpt::PathString::FromUnicode(inputfilename), std::ios::binary);
 				fi.imbue(std::locale::classic());
 				fi.exceptions(std::ios::badbit);
 				while(!mpt::IO::IsEof(fi))
@@ -140,22 +142,22 @@
 			} else if(mode == MPT_USTRING("raw"))
 			{
 				std::vector<std::byte> signature = key.sign(mpt::as_span(data));
-				mpt::SafeOutputFile sfo(mpt::PathString::FromUnicode(outputfilename));
-				mpt::ofstream & fo = sfo.stream();
+				mpt::IO::SafeOutputFile sfo(mpt::PathString::FromUnicode(outputfilename));
+				mpt::IO::ofstream & fo = sfo.stream();
 				mpt::IO::WriteRaw(fo, mpt::as_span(signature));
 				fo.flush();
 			} else if(mode == MPT_USTRING("jws_compact"))
 			{
 				mpt::ustring signature = key.jws_compact_sign(mpt::as_span(data));
-				mpt::SafeOutputFile sfo(mpt::PathString::FromUnicode(outputfilename));
-				mpt::ofstream & fo = sfo.stream();
+				mpt::IO::SafeOutputFile sfo(mpt::PathString::FromUnicode(outputfilename));
+				mpt::IO::ofstream & fo = sfo.stream();
 				mpt::IO::WriteText(fo, mpt::transcode<std::string>(mpt::common_encoding::utf8, signature));
 				fo.flush();
 			} else if(mode == MPT_USTRING("jws"))
 			{
 				mpt::ustring signature = key.jws_sign(mpt::as_span(data));
-				mpt::SafeOutputFile sfo(mpt::PathString::FromUnicode(outputfilename));
-				mpt::ofstream & fo = sfo.stream();
+				mpt::IO::SafeOutputFile sfo(mpt::PathString::FromUnicode(outputfilename));
+				mpt::IO::ofstream & fo = sfo.stream();
 				mpt::IO::WriteText(fo, mpt::transcode<std::string>(mpt::common_encoding::utf8, signature));
 				fo.flush();
 			} else
Index: Makefile
===================================================================
--- Makefile	(revision 17577)
+++ Makefile	(working copy)
@@ -1516,6 +1516,7 @@
 	svn export ./src/mpt/format         bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/format
 	#svn export ./src/mpt/fs             bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/fs
 	svn export ./src/mpt/io             bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io
+	svn export ./src/mpt/io_file        bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io_file
 	svn export ./src/mpt/io_read        bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io_read
 	svn export ./src/mpt/io_write       bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io_write
 	#svn export ./src/mpt/json           bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/json
@@ -1610,6 +1611,7 @@
 	svn export ./src/mpt/format         bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/format         --native-eol CRLF
 	#svn export ./src/mpt/fs             bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/fs             --native-eol CRLF
 	svn export ./src/mpt/io             bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io             --native-eol CRLF
+	svn export ./src/mpt/io_file        bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io_file        --native-eol CRLF
 	svn export ./src/mpt/io_read        bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io_read        --native-eol CRLF
 	svn export ./src/mpt/io_write       bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io_write       --native-eol CRLF
 	#svn export ./src/mpt/json           bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/json           --native-eol CRLF
Index: misc/mptWine.cpp
===================================================================
--- misc/mptWine.cpp	(revision 17577)
+++ misc/mptWine.cpp	(working copy)
@@ -11,6 +11,7 @@
 #include "stdafx.h"
 #include "mptWine.h"
 
+#include "mpt/io_file/fileref.hpp"
 #include "mpt/path/native_path.hpp"
 
 #include "mptOS.h"
@@ -378,7 +379,7 @@
 		}
 		try
 		{
-			mpt::LazyFileRef out(dirWindows + P_("filetree") + P_("\\") + mpt::PathString::FromUTF8(mpt::replace(file.first, std::string("/"), std::string("\\"))));
+			mpt::IO::FileRef out(dirWindows + P_("filetree") + P_("\\") + mpt::PathString::FromUTF8(mpt::replace(file.first, std::string("/"), std::string("\\"))));
 			out = file.second;
 		} catch(std::exception &)
 		{
@@ -671,7 +672,7 @@
 					{
 						try
 						{
-							mpt::LazyFileRef f(filename);
+							mpt::IO::FileRef f(filename);
 							std::vector<char> buf = f;
 							mpt::PathString treeFilename = mpt::PathString::FromNative(filename.AsNative().substr(basePath.AsNative().length()));
 							result.filetree[treeFilename.ToUTF8()] = buf;
Index: mptrack/CommandSet.cpp
===================================================================
--- mptrack/CommandSet.cpp	(revision 17577)
+++ mptrack/CommandSet.cpp	(working copy)
@@ -15,6 +15,7 @@
 #include "../soundlib/mod_specifications.h"
 #include "../soundlib/Tables.h"
 #include "../mptrack/Reporting.h"
+#include "mpt/io_file/outputfile.hpp"
 #include "../common/mptFileIO.h"
 #include <sstream>
 #include "TrackerSettings.h"
@@ -1517,8 +1518,8 @@
 ...
 */
 
-	mpt::SafeOutputFile sf(filename, std::ios::out, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
-	mpt::ofstream& f = sf;
+	mpt::IO::SafeOutputFile sf(filename, std::ios::out, mpt::IO::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
+	mpt::IO::ofstream& f = sf;
 	if(!f)
 	{
 		ErrorBox(IDS_CANT_OPEN_FILE_FOR_WRITING);
Index: mptrack/Ctrl_ins.cpp
===================================================================
--- mptrack/Ctrl_ins.cpp	(revision 17577)
+++ mptrack/Ctrl_ins.cpp	(working copy)
@@ -26,6 +26,9 @@
 #include "../common/misc_util.h"
 #include "../common/mptStringBuffer.h"
 #include "SelectPluginDialog.h"
+#include "mpt/io_file/inputfile.hpp"
+#include "mpt/io_file/inputfile_filecursor.hpp"
+#include "mpt/io_file/outputfile.hpp"
 #include "../common/mptFileIO.h"
 #include "../common/FileReader.h"
 #include "FileDialog.h"
@@ -1603,7 +1606,7 @@
 bool CCtrlInstruments::OpenInstrument(const mpt::PathString &fileName)
 {
 	BeginWaitCursor();
-	InputFile f(fileName, TrackerSettings::Instance().MiscCacheCompleteFileBeforeLoading);
+	mpt::IO::InputFile f(fileName, TrackerSettings::Instance().MiscCacheCompleteFileBeforeLoading);
 	if(!f.IsValid())
 	{
 		EndWaitCursor();
@@ -2127,8 +2130,8 @@
 			try
 			{
 				ScopedLogCapturer logcapturer(m_modDoc);
-				mpt::SafeOutputFile sf(fileName, std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
-				mpt::ofstream &f = sf;
+				mpt::IO::SafeOutputFile sf(fileName, std::ios::binary, mpt::IO::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
+				mpt::IO::ofstream &f = sf;
 				if(!f)
 				{
 					ok = false;
Index: mptrack/Ctrl_smp.cpp
===================================================================
--- mptrack/Ctrl_smp.cpp	(revision 17577)
+++ mptrack/Ctrl_smp.cpp	(working copy)
@@ -29,6 +29,9 @@
 #include "../tracklib/SampleEdit.h"
 #include "Autotune.h"
 #include "../common/mptStringBuffer.h"
+#include "mpt/io_file/inputfile.hpp"
+#include "mpt/io_file/inputfile_filecursor.hpp"
+#include "mpt/io_file/outputfile.hpp"
 #include "../common/mptFileIO.h"
 #include "../common/FileReader.h"
 #include "openmpt/soundbase/Copy.hpp"
@@ -981,7 +984,7 @@
 bool CCtrlSamples::OpenSample(const mpt::PathString &fileName, FlagSet<OpenSampleTypes> types)
 {
 	BeginWaitCursor();
-	InputFile f(fileName, TrackerSettings::Instance().MiscCacheCompleteFileBeforeLoading);
+	mpt::IO::InputFile f(fileName, TrackerSettings::Instance().MiscCacheCompleteFileBeforeLoading);
 	if(!f.IsValid())
 	{
 		EndWaitCursor();
@@ -1510,8 +1513,8 @@
 
 			try
 			{
-				mpt::SafeOutputFile sf(fileName, std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
-				mpt::ofstream &f = sf;
+				mpt::IO::SafeOutputFile sf(fileName, std::ios::binary, mpt::IO::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
+				mpt::IO::ofstream &f = sf;
 				if(!f)
 				{
 					ok = false;
Index: mptrack/ExceptionHandler.cpp
===================================================================
--- mptrack/ExceptionHandler.cpp	(revision 17577)
+++ mptrack/ExceptionHandler.cpp	(working copy)
@@ -19,6 +19,7 @@
 #include "ExceptionHandler.h"
 #include "../misc/WriteMemoryDump.h"
 #include "../common/version.h"
+#include "mpt/io_file/outputfile.hpp"
 #include "../common/mptFileIO.h"
 #include "../common/mptFS.h"
 #include "../soundlib/mod_specifications.h"
@@ -316,8 +317,8 @@
 	errorMessage += UL_("\n");
 
 	{
-		mpt::SafeOutputFile sf(crashDirectory.path + P_("error.txt"), std::ios::binary, mpt::FlushMode::Full);
-		mpt::ofstream& f = sf;
+		mpt::IO::SafeOutputFile sf(crashDirectory.path + P_("error.txt"), std::ios::binary, mpt::IO::FlushMode::Full);
+		mpt::IO::ofstream& f = sf;
 		f.imbue(std::locale::classic());
 		f << mpt::replace(mpt::ToCharset(mpt::Charset::UTF8, errorMessage), std::string("\n"), std::string("\r\n"));
 	}
@@ -324,8 +325,8 @@
 
 	if(auto ih = CMainFrame::GetInputHandler(); ih != nullptr)
 	{
-		mpt::SafeOutputFile sf(crashDirectory.path + P_("last-commands.txt"), std::ios::binary, mpt::FlushMode::Full);
-		mpt::ofstream &f = sf;
+		mpt::IO::SafeOutputFile sf(crashDirectory.path + P_("last-commands.txt"), std::ios::binary, mpt::IO::FlushMode::Full);
+		mpt::IO::ofstream& f = sf;
 		f.imbue(std::locale::classic());
 
 		const auto commandSet = ih->m_activeCommandSet.get();
@@ -343,8 +344,8 @@
 	}
 
 	{
-		mpt::SafeOutputFile sf(crashDirectory.path + P_("threads.txt"), std::ios::binary, mpt::FlushMode::Full);
-		mpt::ofstream& f = sf;
+		mpt::IO::SafeOutputFile sf(crashDirectory.path + P_("threads.txt"), std::ios::binary, mpt::IO::FlushMode::Full);
+		mpt::IO::ofstream& f = sf;
 		f.imbue(std::locale::classic());
 		f << MPT_AFORMAT("current : {}")(mpt::afmt::hex0<8>(GetCurrentThreadId())) << "\r\n";
 		f << MPT_AFORMAT("GUI     : {}")(mpt::afmt::hex0<8>(mpt::log::Trace::GetThreadId(mpt::log::Trace::ThreadKindGUI))) << "\r\n";
@@ -360,8 +361,8 @@
 	};
 
 	{
-		mpt::SafeOutputFile sf(crashDirectory.path + P_("active-settings.txt"), std::ios::binary, mpt::FlushMode::Full);
-		mpt::ofstream& f = sf;
+		mpt::IO::SafeOutputFile sf(crashDirectory.path + P_("active-settings.txt"), std::ios::binary, mpt::IO::FlushMode::Full);
+		mpt::IO::ofstream& f = sf;
 		f.imbue(std::locale::classic());
 		if(theApp.GetpSettings())
 		{
@@ -425,15 +426,15 @@
 	*/
 
 	{
-		mpt::SafeOutputFile sf(crashDirectory.path + P_("about-openmpt.txt"), std::ios::binary, mpt::FlushMode::Full);
-		mpt::ofstream& f = sf;
+		mpt::IO::SafeOutputFile sf(crashDirectory.path + P_("about-openmpt.txt"), std::ios::binary, mpt::IO::FlushMode::Full);
+		mpt::IO::ofstream& f = sf;
 		f.imbue(std::locale::classic());
 		f << mpt::ToCharset(mpt::Charset::UTF8, CAboutDlg::GetTabText(0));
 	}
 
 	{
-		mpt::SafeOutputFile sf(crashDirectory.path + P_("about-components.txt"), std::ios::binary, mpt::FlushMode::Full);
-		mpt::ofstream& f = sf;
+		mpt::IO::SafeOutputFile sf(crashDirectory.path + P_("about-components.txt"), std::ios::binary, mpt::IO::FlushMode::Full);
+		mpt::IO::ofstream& f = sf;
 		f.imbue(std::locale::classic());
 		f << mpt::ToCharset(mpt::Charset::UTF8, CAboutDlg::GetTabText(1));
 	}
Index: mptrack/MainFrm.cpp
===================================================================
--- mptrack/MainFrm.cpp	(revision 17577)
+++ mptrack/MainFrm.cpp	(working copy)
@@ -40,6 +40,8 @@
 #include "PatternClipboard.h"
 #include "PatternFont.h"
 #include "../common/mptFileIO.h"
+#include "mpt/io_file/inputfile.hpp"
+#include "mpt/io_file/inputfile_filecursor.hpp"
 #include "../common/mptFS.h"
 #include "../common/FileReader.h"
 #include "../common/Profiler.h"
@@ -441,7 +443,7 @@
 				size_t failed = 0, total = 0;
 				while(scanner.Next(scanName))
 				{
-					InputFile inputFile(scanName, TrackerSettings::Instance().MiscCacheCompleteFileBeforeLoading);
+					mpt::IO::InputFile inputFile(scanName, TrackerSettings::Instance().MiscCacheCompleteFileBeforeLoading);
 					if(!inputFile.IsValid())
 						continue;
 					auto sndFile = std::make_unique<CSoundFile>();
@@ -1560,7 +1562,7 @@
 
 		if(!ok && !filename.empty())
 		{
-			InputFile f(filename, TrackerSettings::Instance().MiscCacheCompleteFileBeforeLoading);
+			mpt::IO::InputFile f(filename, TrackerSettings::Instance().MiscCacheCompleteFileBeforeLoading);
 			if(f.IsValid())
 			{
 				FileReader file = GetFileReader(f);
Index: mptrack/mod2midi.cpp
===================================================================
--- mptrack/mod2midi.cpp	(revision 17577)
+++ mptrack/mod2midi.cpp	(working copy)
@@ -430,12 +430,12 @@
 		SNDMIXPLUGIN tempoTrackPlugin;
 		VSTPluginLib m_plugFactory;
 		CSoundFile &m_sndFile;
-		mpt::ofstream &m_file;
+		std::ostream &m_file;
 		const GetLengthType m_songLength;
 		const bool m_wasInstrumentMode;
 
 	public:
-		Conversion(CSoundFile &sndFile, const InstrMap &instrMap, mpt::ofstream &file, bool overlappingInstruments, const GetLengthType &songLength)
+		Conversion(CSoundFile &sndFile, const InstrMap &instrMap, std::ostream &file, bool overlappingInstruments, const GetLengthType &songLength)
 			: m_oldInstruments(sndFile.GetNumInstruments())
 			, m_plugFactory(nullptr, true, {}, {}, {})
 			, m_sndFile(sndFile)
Index: mptrack/mod2midi.h
===================================================================
--- mptrack/mod2midi.h	(revision 17577)
+++ mptrack/mod2midi.h	(working copy)
@@ -62,11 +62,11 @@
 {
 public:
 	CSoundFile &m_sndFile;
-	mpt::ofstream &m_file;
+	std::ostream &m_file;
 	const MidiExport::InstrMap &m_instrMap;
 
 public:
-	CDoMidiConvert(CSoundFile &sndFile, mpt::ofstream &f, const MidiExport::InstrMap &instrMap, CWnd *parent = nullptr)
+	CDoMidiConvert(CSoundFile &sndFile, std::ostream &f, const MidiExport::InstrMap &instrMap, CWnd *parent = nullptr)
 		: CProgressDialog(parent)
 		, m_sndFile(sndFile)
 		, m_file(f)
Index: mptrack/Mod2wave.cpp
===================================================================
--- mptrack/Mod2wave.cpp	(revision 17577)
+++ mptrack/Mod2wave.cpp	(working copy)
@@ -24,6 +24,7 @@
 #include "../common/Dither.h"
 #include "../soundlib/AudioReadTarget.h"
 #include "../soundlib/plugins/PlugInterface.h"
+#include "mpt/io_file/fstream.hpp"
 #include "../common/mptFileIO.h"
 #include "mpt/audio/span.hpp"
 #include <variant>
@@ -914,7 +915,7 @@
 	float *normalizeBuffer = nullptr;
 	float normalizePeak = 0.0f;
 	const mpt::PathString normalizeFileName = mpt::TemporaryPathname{P_("OpenMPT")}.GetPathname();
-	std::optional<mpt::fstream> normalizeFile;
+	std::optional<mpt::IO::fstream> normalizeFile;
 	if(m_Settings.normalize)
 	{
 		normalizeBufferData.resize(MIXBUFFERSIZE * 4);
Index: mptrack/mod2wave.h
===================================================================
--- mptrack/mod2wave.h	(revision 17577)
+++ mptrack/mod2wave.h	(working copy)
@@ -117,13 +117,13 @@
 public:
 	const CWaveConvertSettings &m_Settings;
 	CSoundFile &m_SndFile;
-	mpt::ofstream &fileStream;
+	std::ostream &fileStream;
 	const CString &caption;
 	uint64 m_dwSongLimit;
 	bool m_bGivePlugsIdleTime;
 
 public:
-	CDoWaveConvert(CSoundFile &sndFile, mpt::ofstream &f, const CString &caption, const CWaveConvertSettings &settings, CWnd *parent = NULL)
+	CDoWaveConvert(CSoundFile &sndFile, std::ostream &f, const CString &caption, const CWaveConvertSettings &settings, CWnd *parent = NULL)
 		: CProgressDialog(parent)
 		, m_Settings(settings)
 		, m_SndFile(sndFile)
Index: mptrack/Moddoc.cpp
===================================================================
--- mptrack/Moddoc.cpp	(revision 17577)
+++ mptrack/Moddoc.cpp	(working copy)
@@ -38,6 +38,9 @@
 #include "CleanupSong.h"
 #include "../common/mptStringBuffer.h"
 #include "../common/mptFileTemporary.h"
+#include "mpt/io_file/inputfile.hpp"
+#include "mpt/io_file/inputfile_filecursor.hpp"
+#include "mpt/io_file/outputfile.hpp"
 #include "../common/mptFileIO.h"
 #include <sstream>
 #include "../common/mptFS.h"
@@ -199,7 +202,7 @@
 
 		MPT_LOG_GLOBAL(LogDebug, "Loader", U_("Open..."));
 
-		InputFile f(filename, TrackerSettings::Instance().MiscCacheCompleteFileBeforeLoading);
+		mpt::IO::InputFile f(filename, TrackerSettings::Instance().MiscCacheCompleteFileBeforeLoading);
 		if (f.IsValid())
 		{
 			FileReader file = GetFileReader(f);
@@ -318,8 +321,8 @@
 	m_SndFile.m_dwLastSavedWithVersion = Version::Current();
 	try
 	{
-		mpt::SafeOutputFile sf(filename, std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
-		mpt::ofstream &f = sf;
+		mpt::IO::SafeOutputFile sf(filename, std::ios::binary, mpt::IO::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
+		mpt::IO::ofstream &f = sf;
 		if(f)
 		{
 			if(m_SndFile.m_SongFlags[SONG_IMPORTED] && !(GetModType() & (MOD_TYPE_MOD | MOD_TYPE_S3M)))
@@ -418,10 +421,10 @@
 
 			try
 			{
-				mpt::SafeOutputFile sf(filename, std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
+				mpt::IO::SafeOutputFile sf(filename, std::ios::binary, mpt::IO::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
 				if(sf)
 				{
-					mpt::ofstream &f = sf;
+					mpt::IO::ofstream &f = sf;
 					f.exceptions(f.exceptions() | std::ios::badbit | std::ios::failbit);
 
 					if(sample.uFlags[CHN_ADLIB] || format == dfS3I)
@@ -558,7 +561,7 @@
 		auto source = std::make_unique<CSoundFile>();
 		for(const auto &file : files)
 		{
-			InputFile f(file, TrackerSettings::Instance().MiscCacheCompleteFileBeforeLoading);
+			mpt::IO::InputFile f(file, TrackerSettings::Instance().MiscCacheCompleteFileBeforeLoading);
 			if(!f.IsValid())
 			{
 				AddToLog("Unable to open source file!");
@@ -1854,8 +1857,8 @@
 			bool cancel = true;
 			try
 			{
-				mpt::SafeOutputFile safeFileStream(thisName, std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
-				mpt::ofstream &f = safeFileStream;
+				mpt::IO::SafeOutputFile safeFileStream(thisName, std::ios::binary, mpt::IO::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
+				mpt::IO::ofstream &f = safeFileStream;
 				f.exceptions(f.exceptions() | std::ios::badbit | std::ios::failbit);
 
 				if(!f)
@@ -1878,7 +1881,7 @@
 			{
 				if(!cancel)
 				{
-					InputFile f(thisName, TrackerSettings::Instance().MiscCacheCompleteFileBeforeLoading);
+					mpt::IO::InputFile f(thisName, TrackerSettings::Instance().MiscCacheCompleteFileBeforeLoading);
 					if(f.IsValid())
 					{
 						FileReader file = GetFileReader(f);
@@ -2005,8 +2008,8 @@
 	{
 		try
 		{
-			mpt::SafeOutputFile sf(dlg.GetFirstFile(), std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
-			mpt::ofstream &f = sf;
+			mpt::IO::SafeOutputFile sf(dlg.GetFirstFile(), std::ios::binary, mpt::IO::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
+			mpt::IO::ofstream &f = sf;
 			f.exceptions(f.exceptions() | std::ios::badbit | std::ios::failbit);
 
 			if(!f.good())
@@ -2084,8 +2087,8 @@
 	BeginWaitCursor();
 	try
 	{
-		mpt::SafeOutputFile sf(filename, std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
-		mpt::ofstream &f = sf;
+		mpt::IO::SafeOutputFile sf(filename, std::ios::binary, mpt::IO::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
+		mpt::IO::ofstream &f = sf;
 		if(f)
 		{
 			f.exceptions(f.exceptions() | std::ios::badbit | std::ios::failbit);
Index: mptrack/Modedit.cpp
===================================================================
--- mptrack/Modedit.cpp	(revision 17577)
+++ mptrack/Modedit.cpp	(working copy)
@@ -21,6 +21,9 @@
 #include "../soundlib/OPL.h"
 #include "../common/misc_util.h"
 #include "../common/mptStringBuffer.h"
+#include "mpt/io_file/inputfile.hpp"
+#include "mpt/io_file/inputfile_filecursor.hpp"
+#include "mpt/io_file/outputfile.hpp"
 #include "../common/mptFileIO.h"
 #include <sstream>
 // Plugin cloning
@@ -1172,8 +1175,8 @@
 	bool ok = false;
 	try
 	{
-		mpt::SafeOutputFile sf(fileName, std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
-		mpt::ofstream &f = sf;
+		mpt::IO::SafeOutputFile sf(fileName, std::ios::binary, mpt::IO::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
+		mpt::IO::ofstream &f = sf;
 		f.exceptions(f.exceptions() | std::ios::badbit | std::ios::failbit);
 		if(f)
 			ok = mpt::IO::WriteRaw(f, s.GetString(), s.GetLength());
@@ -1209,7 +1212,7 @@
 
 bool CModDoc::LoadEnvelope(INSTRUMENTINDEX nIns, EnvelopeType nEnv, const mpt::PathString &fileName)
 {
-	InputFile f(fileName, TrackerSettings::Instance().MiscCacheCompleteFileBeforeLoading);
+	mpt::IO::InputFile f(fileName, TrackerSettings::Instance().MiscCacheCompleteFileBeforeLoading);
 	if(nIns < 1 || nIns > m_SndFile.m_nInstruments || !m_SndFile.Instruments[nIns] || !f.IsValid())
 		return false;
 	BeginWaitCursor();
Index: mptrack/Mptrack.cpp
===================================================================
--- mptrack/Mptrack.cpp	(revision 17577)
+++ mptrack/Mptrack.cpp	(working copy)
@@ -37,6 +37,7 @@
 #include "MPTrackWine.h"
 #include "MPTrackUtil.h"
 #include "../common/mptFS.h"
+#include "mpt/io_file/outputfile.hpp"
 #include "../misc/mptOS.h"
 #include "mpt/arch/arch.hpp"
 #if MPT_MSVC_AT_LEAST(2022, 2) && MPT_MSVC_BEFORE(2022, 3)
@@ -915,7 +916,7 @@
 	// convert to new style
 	if(configInstallPortable && !configPortableFlag)
 	{
-		mpt::SafeOutputFile f(portableFlagFilename);
+		mpt::IO::SafeOutputFile f(portableFlagFilename);
 	}
 
 	// Determine portable mode.
Index: mptrack/MPTrackWine.cpp
===================================================================
--- mptrack/MPTrackWine.cpp	(revision 17577)
+++ mptrack/MPTrackWine.cpp	(working copy)
@@ -23,6 +23,8 @@
 #include "AboutDialog.h"
 #include "TrackerSettings.h"
 #include "../common/ComponentManager.h"
+#include "mpt/io_file/inputfile.hpp"
+#include "mpt/io_file/inputfile_filecursor.hpp"
 #include "../common/mptFileIO.h"
 #include "../common/mptFS.h"
 #include "../misc/mptOS.h"
@@ -111,7 +113,7 @@
 
 static mpt::crc64_jones WineHashFile(mpt::crc64_jones crc, mpt::PathString filename)
 {
-	InputFile file(filename, TrackerSettings::Instance().MiscCacheCompleteFileBeforeLoading);
+	mpt::IO::InputFile file(filename, TrackerSettings::Instance().MiscCacheCompleteFileBeforeLoading);
 	if(!file.IsValid())
 	{
 		return crc;
Index: mptrack/OPLExport.cpp
===================================================================
--- mptrack/OPLExport.cpp	(revision 17577)
+++ mptrack/OPLExport.cpp	(working copy)
@@ -16,6 +16,7 @@
 #include "ProgressDialog.h"
 #include "../soundlib/OPL.h"
 #include "../soundlib/Tagging.h"
+#include "mpt/io_file/outputfile.hpp"
 
 #include <zlib/zlib.h>
 
@@ -571,8 +572,8 @@
 			mpt::PathString currentFileName = fileName;
 			if(m_subSongs.size() > 1)
 				currentFileName = fileName.ReplaceExtension(mpt::PathString::FromNative(MPT_TFORMAT(" ({})")(mpt::ufmt::fmt(i + 1, songIndexFmt))) + fileName.GetFilenameExtension());
-			mpt::SafeOutputFile sf(currentFileName, std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
-			mpt::ofstream &f = sf;
+			mpt::IO::SafeOutputFile sf(currentFileName, std::ios::binary, mpt::IO::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
+			mpt::IO::ofstream &f = sf;
 			try
 			{
 				if(!f)
Index: mptrack/Settings.cpp
===================================================================
--- mptrack/Settings.cpp	(revision 17577)
+++ mptrack/Settings.cpp	(working copy)
@@ -21,6 +21,7 @@
 #include "Mainfrm.h"
 
 #include <algorithm>
+#include "mpt/io_file/outputfile.hpp"
 #include "../common/mptFileIO.h"
 
 
@@ -429,8 +430,8 @@
 static void WriteFileUTF16LE(const mpt::PathString &filename, const std::wstring &str)
 {
 	static_assert(sizeof(wchar_t) == 2);
-	mpt::SafeOutputFile sinifile(filename, std::ios::binary, mpt::FlushMode::Full);
-	mpt::ofstream& inifile = sinifile;
+	mpt::IO::SafeOutputFile sinifile(filename, std::ios::binary, mpt::IO::FlushMode::Full);
+	mpt::IO::ofstream& inifile = sinifile;
 	const uint8 UTF16LE_BOM[] = { 0xff, 0xfe };
 	inifile.write(reinterpret_cast<const char*>(UTF16LE_BOM), 2);
 	inifile.write(reinterpret_cast<const char*>(str.c_str()), str.length() * sizeof(std::wstring::value_type));
Index: mptrack/TrackerSettings.cpp
===================================================================
--- mptrack/TrackerSettings.cpp	(revision 17577)
+++ mptrack/TrackerSettings.cpp	(working copy)
@@ -28,6 +28,7 @@
 #include "ExceptionHandler.h"
 #include "../soundlib/mod_specifications.h"
 #include "../soundlib/Tables.h"
+#include "mpt/io_file/outputfile.hpp"
 #include "../common/mptFileIO.h"
 #include "../common/mptFS.h"
 #include "../soundlib/tuningcollection.h"
@@ -1004,7 +1005,7 @@
 		if(!mpt::FS::PathExists(fn))
 		{
 			std::unique_ptr<CTuning> pT = CSoundFile::CreateTuning12TET(U_("12TET"));
-			mpt::SafeOutputFile sf(fn, std::ios::binary, mpt::FlushMode::Full);
+			mpt::IO::SafeOutputFile sf(fn, std::ios::binary, mpt::IO::FlushMode::Full);
 			pT->Serialize(sf);
 		}
 	}
@@ -1013,7 +1014,7 @@
 		if(!mpt::FS::PathExists(fn))
 		{
 			std::unique_ptr<CTuning> pT = CSoundFile::CreateTuning12TET(U_("12TET [[fs15 1.17.02.49]]"));
-			mpt::SafeOutputFile sf(fn, std::ios::binary, mpt::FlushMode::Full);
+			mpt::IO::SafeOutputFile sf(fn, std::ios::binary, mpt::IO::FlushMode::Full);
 			pT->Serialize(sf);
 		}
 	}
Index: mptrack/TuningDialog.cpp
===================================================================
--- mptrack/TuningDialog.cpp	(revision 17577)
+++ mptrack/TuningDialog.cpp	(working copy)
@@ -16,6 +16,7 @@
 #include "mpt/io/io_stdstream.hpp"
 #include "TrackerSettings.h"
 #include <algorithm>
+#include "mpt/io_file/outputfile.hpp"
 #include "../common/mptFileIO.h"
 #include "../common/misc_util.h"
 #include "TuningDialog.h"
@@ -618,8 +619,8 @@
 		BeginWaitCursor();
 		try
 		{
-			mpt::SafeOutputFile sfout(dlg.GetFirstFile(), std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
-			mpt::ofstream &fout = sfout;
+			mpt::IO::SafeOutputFile sfout(dlg.GetFirstFile(), std::ios::binary, mpt::IO::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
+			mpt::IO::ofstream &fout = sfout;
 			fout.exceptions(fout.exceptions() | std::ios::badbit | std::ios::failbit);
 
 			if(tuningFilter != -1 && filterIndex == tuningFilter)
@@ -698,8 +699,8 @@
 
 			try
 			{
-				mpt::SafeOutputFile sfout(fileName, std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
-				mpt::ofstream &fout = sfout;
+				mpt::IO::SafeOutputFile sfout(fileName, std::ios::binary, mpt::IO::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
+				mpt::IO::ofstream &fout = sfout;
 				fout.exceptions(fout.exceptions() | std::ios::badbit | std::ios::failbit);
 				if(tuning.Serialize(fout) != Tuning::SerializationResult::Success)
 				{
Index: mptrack/UpdateCheck.cpp
===================================================================
--- mptrack/UpdateCheck.cpp	(revision 17577)
+++ mptrack/UpdateCheck.cpp	(working copy)
@@ -30,6 +30,9 @@
 #include "Moddoc.h"
 #include "mpt/io/io.hpp"
 #include "mpt/io/io_stdstream.hpp"
+#include "mpt/io_file/inputfile.hpp"
+#include "mpt/io_file/inputfile_filecursor.hpp"
+#include "mpt/io_file/outputfile.hpp"
 
 
 OPENMPT_NAMESPACE_BEGIN
@@ -655,7 +658,7 @@
 		{
 			try
 			{
-				InputFile f(settings.persistencePath + P_("update-") + mpt::PathString::FromUnicode(GetChannelName(settings.channel)) + P_(".json"));
+				mpt::IO::InputFile f(settings.persistencePath + P_("update-") + mpt::PathString::FromUnicode(GetChannelName(settings.channel)) + P_(".json"));
 				if(f.IsValid())
 				{
 					std::vector<std::byte> data = GetFileReader(f).ReadRawDataAsByteVector();
@@ -686,7 +689,7 @@
 			result = SearchUpdateModern(internet, settings);
 			try
 			{
-				mpt::SafeOutputFile f(settings.persistencePath + P_("update-") + mpt::PathString::FromUnicode(GetChannelName(settings.channel)) + P_(".json"), std::ios::binary);
+				mpt::IO::SafeOutputFile f(settings.persistencePath + P_("update-") + mpt::PathString::FromUnicode(GetChannelName(settings.channel)) + P_(".json"), std::ios::binary);
 				f.stream().imbue(std::locale::classic());
 				mpt::IO::WriteRaw(f.stream(), mpt::as_span(result.json));
 				f.stream().flush();
@@ -1119,7 +1122,7 @@
 				{
 			
 					UpdateProgress(_T("Creating file..."), 7.0);
-					mpt::SafeOutputFile file(updateFilename, std::ios::binary);
+					mpt::IO::SafeOutputFile file(updateFilename, std::ios::binary);
 					file.stream().imbue(std::locale::classic());
 					file.stream().exceptions(std::ios::failbit | std::ios::badbit);
 				
@@ -1239,7 +1242,7 @@
 				{
 					try
 					{
-						mpt::SafeOutputFile file(dirTempOpenMPTUpdates + P_("update.vbs"), std::ios::binary);
+						mpt::IO::SafeOutputFile file(dirTempOpenMPTUpdates + P_("update.vbs"), std::ios::binary);
 						file.stream().imbue(std::locale::classic());
 						file.stream().exceptions(std::ios::failbit | std::ios::badbit);
 						mpt::IO::WriteRaw(file.stream(), mpt::as_span(std::string(updateScript)));
Index: mptrack/View_tre.cpp
===================================================================
--- mptrack/View_tre.cpp	(revision 17577)
+++ mptrack/View_tre.cpp	(working copy)
@@ -18,6 +18,8 @@
 #include "Moddoc.h"
 #include "Dlsbank.h"
 #include "dlg_misc.h"
+#include "mpt/io_file/inputfile.hpp"
+#include "mpt/io_file/inputfile_filecursor.hpp"
 #include "../common/mptFileIO.h"
 #include "../common/mptFS.h"
 #include "../common/FileReader.h"
@@ -399,7 +401,7 @@
 	if(!songName.empty() && mpt::PathCompareNoCase(m_SongFileName, songName))
 	{
 		// Load module for previewing its instruments
-		InputFile f(libPath + songName, TrackerSettings::Instance().MiscCacheCompleteFileBeforeLoading);
+		mpt::IO::InputFile f(libPath + songName, TrackerSettings::Instance().MiscCacheCompleteFileBeforeLoading);
 		if(f.IsValid())
 		{
 			FileReader file = GetFileReader(f);
Index: soundlib/Dlsbank.cpp
===================================================================
--- soundlib/Dlsbank.cpp	(revision 17577)
+++ soundlib/Dlsbank.cpp	(working copy)
@@ -13,6 +13,8 @@
 #include "Sndfile.h"
 #ifdef MODPLUG_TRACKER
 #include "../mptrack/Mptrack.h"
+#include "mpt/io_file/inputfile.hpp"
+#include "mpt/io_file/inputfile_filecursor.hpp"
 #include "../common/mptFileIO.h"
 #endif
 #include "Dlsbank.h"
@@ -1364,7 +1366,7 @@
 {
 	if(filename.empty()) return false;
 	m_szFileName = filename;
-	InputFile f(filename, SettingCacheCompleteFileBeforeLoading());
+	mpt::IO::InputFile f(filename, SettingCacheCompleteFileBeforeLoading());
 	if(!f.IsValid()) return false;
 	return Open(GetFileReader(f));
 }
Index: soundlib/Load_itp.cpp
===================================================================
--- soundlib/Load_itp.cpp	(revision 17577)
+++ soundlib/Load_itp.cpp	(working copy)
@@ -22,6 +22,8 @@
 #include "../mptrack/Moddoc.h"
 #endif // MODPLUG_TRACKER
 #ifdef MPT_EXTERNAL_SAMPLES
+#include "mpt/io_file/inputfile.hpp"
+#include "mpt/io_file/inputfile_filecursor.hpp"
 #include "../common/mptFileIO.h"
 #endif // MPT_EXTERNAL_SAMPLES
 
@@ -361,7 +363,7 @@
 			continue;
 
 #ifdef MPT_EXTERNAL_SAMPLES
-		InputFile f(instrPaths[ins], SettingCacheCompleteFileBeforeLoading());
+		mpt::IO::InputFile f(instrPaths[ins], SettingCacheCompleteFileBeforeLoading());
 		FileReader instrFile = GetFileReader(f);
 		if(!ReadInstrumentFromFile(ins + 1, instrFile, true))
 		{
Index: soundlib/Load_mid.cpp
===================================================================
--- soundlib/Load_mid.cpp	(revision 17577)
+++ soundlib/Load_mid.cpp	(working copy)
@@ -16,6 +16,8 @@
 #include "../mptrack/TrackerSettings.h"
 #include "../mptrack/Moddoc.h"
 #include "../mptrack/Mptrack.h"
+#include "mpt/io_file/inputfile.hpp"
+#include "mpt/io_file/inputfile_filecursor.hpp"
 #include "../common/mptFileIO.h"
 #include "../common/mptFS.h"
 #endif // MODPLUG_TRACKER
@@ -1363,7 +1365,7 @@
 			} else
 			{
 				// Load from Instrument or Sample file
-				InputFile f(midiMapName, SettingCacheCompleteFileBeforeLoading());
+				mpt::IO::InputFile f(midiMapName, SettingCacheCompleteFileBeforeLoading());
 				if(f.IsValid())
 				{
 					FileReader insFile = GetFileReader(f);
Index: soundlib/Load_mod.cpp
===================================================================
--- soundlib/Load_mod.cpp	(revision 17577)
+++ soundlib/Load_mod.cpp	(working copy)
@@ -21,6 +21,8 @@
 #endif
 #ifdef MPT_EXTERNAL_SAMPLES
 // For loading external data in Startrekker files
+#include "mpt/io_file/inputfile.hpp"
+#include "mpt/io_file/inputfile_filecursor.hpp"
 #include "../common/mptFS.h"
 #include "../common/mptPathString.h"
 #endif  // MPT_EXTERNAL_SAMPLES
@@ -1213,7 +1215,7 @@
 	if((loadFlags & loadSampleData) && isStartrekker)
 	{
 #ifdef MPT_EXTERNAL_SAMPLES
-		std::optional<InputFile> amFile;
+		std::optional<mpt::IO::InputFile> amFile;
 		FileReader amData;
 		if(file.GetOptionalFileName())
 		{
Index: soundlib/plugins/PlugInterface.cpp
===================================================================
--- soundlib/plugins/PlugInterface.cpp	(revision 17577)
+++ soundlib/plugins/PlugInterface.cpp	(working copy)
@@ -22,6 +22,9 @@
 // LoadProgram/SaveProgram
 #include "../../mptrack/FileDialog.h"
 #include "../../mptrack/VstPresets.h"
+#include "mpt/io_file/inputfile.hpp"
+#include "mpt/io_file/inputfile_filecursor.hpp"
+#include "mpt/io_file/outputfile.hpp"
 #include "../../common/mptFileIO.h"
 #include "../../common/mptFS.h"
 #include "../mod_specifications.h"
@@ -656,8 +659,8 @@
 
 	try
 	{
-		mpt::SafeOutputFile sf(dlg.GetFirstFile(), std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
-		mpt::ofstream &f = sf;
+		mpt::IO::SafeOutputFile sf(dlg.GetFirstFile(), std::ios::binary, mpt::IO::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
+		mpt::IO::ofstream &f = sf;
 		f.exceptions(f.exceptions() | std::ios::badbit | std::ios::failbit);
 		if(f.good() && VSTPresets::SaveFile(f, *this, isBank))
 			return true;
@@ -698,7 +701,7 @@
 	}
 
 	const char *errorStr = nullptr;
-	InputFile f(fileName, SettingCacheCompleteFileBeforeLoading());
+	mpt::IO::InputFile f(fileName, SettingCacheCompleteFileBeforeLoading());
 	if(f.IsValid())
 	{
 		FileReader file = GetFileReader(f);
Index: soundlib/SampleFormatMediaFoundation.cpp
===================================================================
--- soundlib/SampleFormatMediaFoundation.cpp	(revision 17577)
+++ soundlib/SampleFormatMediaFoundation.cpp	(working copy)
@@ -23,6 +23,8 @@
 #include "../soundlib/ModSampleCopy.h"
 #include "../common/ComponentManager.h"
 #if defined(MPT_WITH_MEDIAFOUNDATION)
+#include "mpt/io_file/fileadapter.hpp"
+#include "../common/FileReader.h"
 #include <windows.h>
 #include <atlbase.h>
 #include <mfapi.h>
@@ -303,8 +305,8 @@
 	file.Rewind();
 	// When using MF to decode MP3 samples in MO3 files, we need the mp3 file extension
 	// for some of them or otherwise MF refuses to recognize them.
-	mpt::PathString tmpfileExtension = (mo3Decode ? P_("mp3") : P_("tmp"));
-	OnDiskFileWrapper diskfile(file, tmpfileExtension);
+	mpt::PathString tmpfileExtension = (mo3Decode ? P_(".mp3") : P_(".tmp"));
+	mpt::IO::FileAdapter<FileCursor> diskfile(file, mpt::IO::unique_tempfilename{mpt::IO::unique_basename{P_("OpenMPT"), mpt::UUID::GenerateLocalUseOnly(mpt::global_prng())}, tmpfileExtension});
 	if(!diskfile.IsValid())
 	{
 		return false;
@@ -322,7 +324,7 @@
 	MPT_MF_CHECKED(MFCreateSourceResolver(&sourceResolver));
 	MF_OBJECT_TYPE objectType = MF_OBJECT_INVALID;
 	CComPtr<IUnknown> unknownMediaSource;
-	MPT_MF_CHECKED(sourceResolver->CreateObjectFromURL(diskfile.GetFilename().ToWide().c_str(), MF_RESOLUTION_MEDIASOURCE | MF_RESOLUTION_CONTENT_DOES_NOT_HAVE_TO_MATCH_EXTENSION_OR_MIME_TYPE | MF_RESOLUTION_READ, NULL, &objectType, &unknownMediaSource));
+	MPT_MF_CHECKED(sourceResolver->CreateObjectFromURL(mpt::transcode<std::wstring>(diskfile.GetFilename()).c_str(), MF_RESOLUTION_MEDIASOURCE | MF_RESOLUTION_CONTENT_DOES_NOT_HAVE_TO_MATCH_EXTENSION_OR_MIME_TYPE | MF_RESOLUTION_READ, NULL, &objectType, &unknownMediaSource));
 	if(objectType != MF_OBJECT_MEDIASOURCE)
 	{
 		return false;
Index: soundlib/SampleFormatSFZ.cpp
===================================================================
--- soundlib/SampleFormatSFZ.cpp	(revision 17577)
+++ soundlib/SampleFormatSFZ.cpp	(working copy)
@@ -12,8 +12,12 @@
 #include "Sndfile.h"
 #ifdef MODPLUG_TRACKER
 #include "../mptrack/TrackerSettings.h"
+#include "mpt/io_file/inputfile.hpp"
+#include "mpt/io_file/inputfile_filecursor.hpp"
+#include "../common/mptFileIO.h"
 #endif // MODPLUG_TRACKER
 #ifndef MODPLUG_NO_FILESAVE
+#include "mpt/io_file/outputfile.hpp"
 #include "../common/mptFileIO.h"
 #include "../common/mptFS.h"
 #endif // !MODPLUG_NO_FILESAVE
@@ -492,10 +496,10 @@
 struct SFZInputFile
 {
 	FileReader file;
-	std::unique_ptr<InputFile> inputFile;  // FileReader has pointers into this so its address must not change
+	std::unique_ptr<mpt::IO::InputFile> inputFile;  // FileReader has pointers into this so its address must not change
 	std::string remain;
 
-	SFZInputFile(FileReader f = {}, std::unique_ptr<InputFile> i = {}, std::string r = {})
+	SFZInputFile(FileReader f = {}, std::unique_ptr<mpt::IO::InputFile> i = {}, std::string r = {})
 		: file{std::move(f)}, inputFile{std::move(i)}, remain{std::move(r)} {}
 	SFZInputFile(SFZInputFile &&) = default;
 };
@@ -653,7 +657,7 @@
 					// Avoid recursive #include
 					if(std::find_if(files.begin(), files.end(), [&filename](const SFZInputFile &f) { return f.file.GetOptionalFileName().value_or(P_("")) == filename; }) == files.end())
 					{
-						auto f = std::make_unique<InputFile>(filename);
+						auto f = std::make_unique<mpt::IO::InputFile>(filename);
 						if(f->IsValid())
 						{
 							s.erase(0, charsRead);
@@ -819,7 +823,7 @@
 			}
 			filename = filename.Simplify();
 			SetSamplePath(smp, filename);
-			InputFile f(filename, SettingCacheCompleteFileBeforeLoading());
+			mpt::IO::InputFile f(filename, SettingCacheCompleteFileBeforeLoading());
 			FileReader smpFile = GetFileReader(f);
 			if(!ReadSampleFromFile(smp, smpFile, false))
 			{
@@ -1066,9 +1070,9 @@
 bool CSoundFile::SaveSFZInstrument(INSTRUMENTINDEX nInstr, std::ostream &f, const mpt::PathString &filename, bool useFLACsamples) const
 {
 #ifdef MODPLUG_TRACKER
-	const mpt::FlushMode flushMode = mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave);
+	const mpt::IO::FlushMode flushMode = mpt::IO::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave);
 #else
-	const mpt::FlushMode flushMode = mpt::FlushMode::Full;
+	const mpt::IO::FlushMode flushMode = mpt::IO::FlushMode::Full;
 #endif
 	const ModInstrument *ins = Instruments[nInstr];
 	if(ins == nullptr)
@@ -1184,10 +1188,10 @@
 		bool success = false;
 		try
 		{
-			mpt::SafeOutputFile sfSmp(sampleName, std::ios::binary, flushMode);
+			mpt::IO::SafeOutputFile sfSmp(sampleName, std::ios::binary, flushMode);
 			if(sfSmp)
 			{
-				mpt::ofstream &fSmp = sfSmp;
+				mpt::IO::ofstream &fSmp = sfSmp;
 				fSmp.exceptions(fSmp.exceptions() | std::ios::badbit | std::ios::failbit);
 
 				if(isAdlib)
Index: soundlib/Sndfile.cpp
===================================================================
--- soundlib/Sndfile.cpp	(revision 17577)
+++ soundlib/Sndfile.cpp	(working copy)
@@ -18,7 +18,10 @@
 #include "../mptrack/Mainfrm.h"
 #endif // MODPLUG_TRACKER
 #ifdef MPT_EXTERNAL_SAMPLES
+#include "mpt/io_file/inputfile.hpp"
+#include "mpt/io_file/inputfile_filecursor.hpp"
 #include "../common/mptFileIO.h"
+#include "../common/mptFileIO.h"
 #endif // MPT_EXTERNAL_SAMPLES
 #include "../common/version.h"
 #include "../soundlib/AudioCriticalSection.h"
@@ -1966,7 +1969,7 @@
 bool CSoundFile::LoadExternalSample(SAMPLEINDEX smp, const mpt::PathString &filename)
 {
 	bool ok = false;
-	InputFile f(filename, SettingCacheCompleteFileBeforeLoading());
+	mpt::IO::InputFile f(filename, SettingCacheCompleteFileBeforeLoading());
 
 	if(f.IsValid())
 	{
Index: soundlib/tuningCollection.cpp
===================================================================
--- soundlib/tuningCollection.cpp	(revision 17577)
+++ soundlib/tuningCollection.cpp	(working copy)
@@ -17,6 +17,7 @@
 #include "../common/mptFileIO.h"
 #include "Loaders.h"
 #ifdef MODPLUG_TRACKER
+#include "mpt/io_file/outputfile.hpp"
 #include "../common/mptFS.h"
 #include "../mptrack/TrackerSettings.h"
 #endif //MODPLUG_TRACKER
@@ -301,7 +302,7 @@
 			error = true;
 		} else
 		{
-			mpt::SafeOutputFile sfout(fn, std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
+			mpt::IO::SafeOutputFile sfout(fn, std::ios::binary, mpt::IO::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
 			if(tuning.Serialize(sfout) != Tuning::SerializationResult::Success)
 			{
 				error = true;
Index: src/mpt/io_file/fileadapter.hpp
===================================================================
--- src/mpt/io_file/fileadapter.hpp	(nonexistent)
+++ src/mpt/io_file/fileadapter.hpp	(working copy)
@@ -0,0 +1,182 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_FILE_FILEADAPTER_HPP
+#define MPT_IO_FILE_FILEADAPTER_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/saturate_cast.hpp"
+#include "mpt/io/base.hpp"
+#if !MPT_OS_WINDOWS
+#include "mpt/io/io.hpp"
+#include "mpt/io/io_stdstream.hpp"
+#include "mpt/io_file/fstream.hpp"
+#endif // !MPT_OS_WINDOWS
+#include "mpt/io_file/unique_basename.hpp"
+#include "mpt/io_file/unique_tempfilename.hpp"
+#if MPT_OS_WINDOWS
+#include "mpt/path/os_path.hpp"
+#endif // MPT_OS_WINDOWS
+#include "mpt/string_transcode/transcode.hpp"
+#if MPT_OS_WINDOWS
+#include "mpt/system_error/system_error.hpp"
+#endif // MPT_OS_WINDOWS
+
+#include <stdexcept>
+#if !MPT_OS_WINDOWS
+#include <system_error>
+#endif // MPT_OS_WINDOWS
+
+#include <cstddef>
+#if !MPT_OS_WINDOWS
+#include <cstdio>
+#endif // !MPT_OS_WINDOWS
+
+#if MPT_OS_WINDOWS
+#include <windows.h>
+#endif // MPT_OS_WINDOWS
+
+#if MPT_OS_WINDOWS
+#if MPT_OS_WINDOWS_WINRT
+#if (_WIN32_WINNT >= 0x0602)
+#define MPT_IO_FILE_ONDISKFILEWRAPPER_USE_CREATEFILE
+#endif
+#else // !MPT_OS_WINDOWS_WINRT
+#define MPT_IO_FILE_ONDISKFILEWRAPPER_USE_CREATEFILE
+#endif // MPT_OS_WINDOWS_WINRT
+#endif // MPT_OS_WINDOWS
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+template <typename TFileCursor>
+class FileAdapter {
+
+private:
+	mpt::os_path m_Filename;
+	bool m_IsTempFile;
+
+public:
+	FileAdapter(TFileCursor & file, const mpt::os_path & tempName = unique_tempfilename{unique_basename{}, MPT_OS_PATH(".tmp")})
+		: m_IsTempFile(false) {
+		try {
+			file.Rewind();
+			if (!file.GetOptionalFileName()) {
+
+#ifdef MPT_IO_FILE_ONDISKFILEWRAPPER_USE_CREATEFILE
+
+				HANDLE hFile = NULL;
+#if MPT_OS_WINDOWS_WINRT
+				hFile = mpt::windows::CheckFileHANDLE(CreateFile2(mpt::support_long_path(tempName).c_str(), GENERIC_WRITE, FILE_SHARE_READ, CREATE_ALWAYS, NULL));
+#else
+				hFile = mpt::windows::CheckFileHANDLE(CreateFile(mpt::support_long_path(tempName).c_str(), GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL));
+#endif
+				while (!file.EndOfFile()) {
+					typename TFileCursor::PinnedView view = file.ReadPinnedView(mpt::IO::BUFFERSIZE_NORMAL);
+					std::size_t towrite = view.size();
+					std::size_t written = 0;
+					do {
+						DWORD chunkSize = mpt::saturate_cast<DWORD>(towrite);
+						DWORD chunkDone = 0;
+						try {
+							mpt::windows::CheckBOOL(WriteFile(hFile, view.data() + written, chunkSize, &chunkDone, NULL));
+						} catch (...) {
+							CloseHandle(hFile);
+							hFile = NULL;
+							throw;
+						}
+						if (chunkDone != chunkSize) {
+							CloseHandle(hFile);
+							hFile = NULL;
+							throw std::runtime_error("Incomplete WriteFile().");
+						}
+						towrite -= chunkDone;
+						written += chunkDone;
+					} while (towrite > 0);
+				}
+				CloseHandle(hFile);
+				hFile = NULL;
+
+#else // !MPT_IO_FILE_ONDISKFILEWRAPPER_USE_CREATEFILE
+
+				mpt::IO::ofstream f(tempName, std::ios::binary);
+				if (!f) {
+					throw std::runtime_error("Error creating temporary file.");
+				}
+				while (!file.EndOfFile()) {
+					typename TFileCursor::PinnedView view = file.ReadPinnedView(mpt::IO::BUFFERSIZE_NORMAL);
+					std::size_t towrite = view.size();
+					std::size_t written = 0;
+					do {
+						std::size_t chunkSize = mpt::saturate_cast<std::size_t>(towrite);
+						bool chunkOk = false;
+						chunkOk = mpt::IO::WriteRaw(f, mpt::const_byte_span(view.data() + written, chunkSize));
+						if (!chunkOk) {
+							throw std::runtime_error("Incomplete Write.");
+						}
+						towrite -= chunkSize;
+						written += chunkSize;
+					} while (towrite > 0);
+				}
+				f.close();
+
+#endif // MPT_ONDISKFILEWRAPPER_NO_CREATEFILE
+
+				m_Filename = tempName;
+				m_IsTempFile = true;
+			} else {
+				m_Filename = file.GetOptionalFileName().value();
+			}
+		} catch (const std::runtime_error &) {
+			m_Filename = mpt::os_path{};
+			m_IsTempFile = false;
+		}
+	}
+
+	~FileAdapter() {
+		if (m_IsTempFile) {
+#if MPT_OS_WINDOWS
+			DeleteFile(m_Filename.c_str());
+#else // !MPT_OS_WINDOWS
+			std::remove(m_Filename.c_str());
+			//std::error_code ec{};
+			//std::filesystem::remove(mpt::transcode<std::filesystem::path>(m_Filename.AsNative()), ec);
+#endif // MPT_OS_WINDOWS
+			m_IsTempFile = false;
+		}
+		m_Filename = mpt::os_path{};
+	}
+
+public:
+	bool IsValid() const {
+		return !m_Filename.empty();
+	}
+
+	mpt::os_path GetFilename() const {
+		return m_Filename;
+	}
+
+}; // class FileAdapter
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_FILE_FILEADAPTER_HPP

Property changes on: src/mpt/io_file/fileadapter.hpp
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/x-c++hdr
\ No newline at end of property
Index: src/mpt/io_file/fileref.hpp
===================================================================
--- src/mpt/io_file/fileref.hpp	(nonexistent)
+++ src/mpt/io_file/fileref.hpp	(working copy)
@@ -0,0 +1,124 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_FILE_FILEREF_HPP
+#define MPT_IO_FILE_FILEREF_HPP
+
+
+
+#include "mpt/base/memory.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/span.hpp"
+#include "mpt/io/base.hpp"
+#include "mpt/io/io.hpp"
+#include "mpt/io/io_stdstream.hpp"
+#include "mpt/io_file/fstream.hpp"
+#include "mpt/path/os_path.hpp"
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+// FileRef is a simple reference to an on-disk file by the means of a
+// filename which allows easy assignment of the whole file contents to and from
+// byte buffers.
+
+class FileRef {
+private:
+	const mpt::os_path m_Filename;
+
+public:
+	FileRef(const mpt::os_path & filename)
+		: m_Filename(filename) {
+		return;
+	}
+
+public:
+	FileRef & operator=(const std::vector<std::byte> & data) {
+		mpt::IO::ofstream file(m_Filename, std::ios::binary);
+		file.exceptions(std::ios_base::failbit | std::ios_base::badbit);
+		mpt::IO::WriteRaw(file, mpt::as_span(data));
+		mpt::IO::Flush(file);
+		return *this;
+	}
+
+	FileRef & operator=(const std::vector<char> & data) {
+		mpt::IO::ofstream file(m_Filename, std::ios::binary);
+		file.exceptions(std::ios_base::failbit | std::ios_base::badbit);
+		mpt::IO::WriteRaw(file, mpt::as_span(data));
+		mpt::IO::Flush(file);
+		return *this;
+	}
+
+	FileRef & operator=(const std::string & data) {
+		mpt::IO::ofstream file(m_Filename, std::ios::binary);
+		file.exceptions(std::ios_base::failbit | std::ios_base::badbit);
+		mpt::IO::WriteRaw(file, mpt::as_span(data));
+		mpt::IO::Flush(file);
+		return *this;
+	}
+
+	operator std::vector<std::byte>() const {
+		mpt::IO::ifstream file(m_Filename, std::ios::binary);
+		if (!mpt::IO::IsValid(file)) {
+			return std::vector<std::byte>();
+		}
+		file.exceptions(std::ios_base::failbit | std::ios_base::badbit);
+		mpt::IO::SeekEnd(file);
+		std::vector<std::byte> buf(mpt::saturate_cast<std::size_t>(mpt::IO::TellRead(file)));
+		mpt::IO::SeekBegin(file);
+		mpt::IO::ReadRaw(file, mpt::as_span(buf));
+		return buf;
+	}
+
+	operator std::vector<char>() const {
+		mpt::IO::ifstream file(m_Filename, std::ios::binary);
+		if (!mpt::IO::IsValid(file)) {
+			return std::vector<char>();
+		}
+		file.exceptions(std::ios_base::failbit | std::ios_base::badbit);
+		mpt::IO::SeekEnd(file);
+		std::vector<char> buf(mpt::saturate_cast<std::size_t>(mpt::IO::TellRead(file)));
+		mpt::IO::SeekBegin(file);
+		mpt::IO::ReadRaw(file, mpt::as_span(buf));
+		return buf;
+	}
+
+	operator std::string() const {
+		mpt::IO::ifstream file(m_Filename, std::ios::binary);
+		if (!mpt::IO::IsValid(file)) {
+			return std::string();
+		}
+		file.exceptions(std::ios_base::failbit | std::ios_base::badbit);
+		mpt::IO::SeekEnd(file);
+		std::vector<char> buf(mpt::saturate_cast<std::size_t>(mpt::IO::TellRead(file)));
+		mpt::IO::SeekBegin(file);
+		mpt::IO::ReadRaw(file, mpt::as_span(buf));
+		return mpt::buffer_cast<std::string>(buf);
+	}
+};
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_FILE_FILEREF_HPP

Property changes on: src/mpt/io_file/fileref.hpp
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/x-c++hdr
\ No newline at end of property
Index: src/mpt/io_file/fstream.hpp
===================================================================
--- src/mpt/io_file/fstream.hpp	(nonexistent)
+++ src/mpt/io_file/fstream.hpp	(working copy)
@@ -0,0 +1,156 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_FILE_FSTREAM_HPP
+#define MPT_IO_FILE_FSTREAM_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/span.hpp"
+#include "mpt/io/base.hpp"
+#include "mpt/io/io.hpp"
+#include "mpt/io/io_stdstream.hpp"
+#include "mpt/io_file/fstream.hpp"
+#include "mpt/path/os_path.hpp"
+#include "mpt/path/os_path_long.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+
+#if defined(MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR)
+#if MPT_GCC_AT_LEAST(9, 1, 0) && !defined(MPT_COMPILER_QUIRK_NO_FILESYSTEM)
+#include <filesystem>
+#endif // MPT_GCC_AT_LEAST(9,1,0) && !defined(MPT_COMPILER_QUIRK_NO_FILESYSTEM)
+#endif // MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR
+#include <fstream>
+#include <ios>
+#include <string>
+#include <string_view>
+
+#if MPT_LIBCXX_MS
+#include <cstdio>
+#endif // MPT_LIBCXX_MS
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+namespace detail {
+
+template<typename Tbase>
+inline void fstream_open(Tbase & base, const mpt::os_path & filename, std::ios_base::openmode mode) {
+#if defined(MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR)
+#if MPT_GCC_AT_LEAST(9, 1, 0) && !defined(MPT_COMPILER_QUIRK_NO_FILESYSTEM)
+	base.open(static_cast<std::filesystem::path>(filename.AsNative()), mode);
+#else // !MPT_GCC_AT_LEAST(9,1,0) || MPT_COMPILER_QUIRK_NO_FILESYSTEM
+	// Warning: MinGW with GCC earlier than 9.1 detected. Standard library does neither provide std::fstream wchar_t overloads nor std::filesystem with wchar_t support. Unicode filename support is thus unavailable.
+	base.open(mpt::transcode<std::string>(mpt::logical_encoding::locale, filename.AsNative()).c_str(), mode);
+#endif // MPT_GCC_AT_LEAST(9,1,0) && !MPT_COMPILER_QUIRK_NO_FILESYSTEM
+#else // !MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR
+	base.open(filename.c_str(), mode);
+#endif // MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR
+}
+
+} // namespace detail
+
+// We cannot rely on implicit conversion of mpt::path to std::filesystem::path when constructing std::fstream
+// because of broken overload implementation in GCC libstdc++ 8, 9, 10.
+// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95642
+// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90704
+
+class fstream
+	: public std::fstream {
+private:
+	using base_type = std::fstream;
+
+public:
+	explicit fstream(const mpt::os_path & filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) {
+		detail::fstream_open<base_type>(*this, filename, mode);
+	}
+#if MPT_LIBCXX_MS
+protected:
+	fstream(std::FILE * file)
+		: std::fstream(file) {
+	}
+#endif // MPT_LIBCXX_MS
+public:
+	void open(const char * filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) = delete;
+	void open(const std::string_view & filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) = delete;
+	void open(const std::string & filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) = delete;
+#if MPT_OS_WINDOWS && !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+	void open(const wchar_t * filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) = delete;
+	void open(const std::wstring_view & filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) = delete;
+	void open(const std::wstring & filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) = delete;
+#endif
+};
+
+class ifstream
+	: public std::ifstream {
+private:
+	using base_type = std::ifstream;
+
+public:
+	explicit ifstream(const mpt::os_path & filename, std::ios_base::openmode mode = std::ios_base::in) {
+		detail::fstream_open<base_type>(*this, filename, mode);
+	}
+#if MPT_LIBCXX_MS
+protected:
+	ifstream(std::FILE * file)
+		: std::ifstream(file) {
+	}
+#endif // MPT_LIBCXX_MS
+public:
+	void open(const char * filename, std::ios_base::openmode mode = std::ios_base::in) = delete;
+	void open(const std::string_view & filename, std::ios_base::openmode mode = std::ios_base::in) = delete;
+	void open(const std::string & filename, std::ios_base::openmode mode = std::ios_base::in) = delete;
+#if MPT_OS_WINDOWS && !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+	void open(const wchar_t * filename, std::ios_base::openmode mode = std::ios_base::in) = delete;
+	void open(const std::wstring_view & filename, std::ios_base::openmode mode = std::ios_base::in) = delete;
+	void open(const std::wstring & filename, std::ios_base::openmode mode = std::ios_base::in) = delete;
+#endif
+};
+
+class ofstream
+	: public std::ofstream {
+private:
+	using base_type = std::ofstream;
+
+public:
+	explicit ofstream(const mpt::os_path & filename, std::ios_base::openmode mode = std::ios_base::out) {
+		detail::fstream_open<base_type>(*this, filename, mode);
+	}
+#if MPT_LIBCXX_MS
+protected:
+	ofstream(std::FILE * file)
+		: std::ofstream(file) {
+	}
+#endif // MPT_LIBCXX_MS
+public:
+	void open(const char * filename, std::ios_base::openmode mode = std::ios_base::out) = delete;
+	void open(const std::string_view & filename, std::ios_base::openmode mode = std::ios_base::out) = delete;
+	void open(const std::string & filename, std::ios_base::openmode mode = std::ios_base::out) = delete;
+#if MPT_OS_WINDOWS && !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+	void open(const wchar_t * filename, std::ios_base::openmode mode = std::ios_base::out) = delete;
+	void open(const std::wstring_view & filename, std::ios_base::openmode mode = std::ios_base::out) = delete;
+	void open(const std::wstring & filename, std::ios_base::openmode mode = std::ios_base::out) = delete;
+#endif
+};
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_FILE_FSTREAM_HPP

Property changes on: src/mpt/io_file/fstream.hpp
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/x-c++hdr
\ No newline at end of property
Index: src/mpt/io_file/inputfile.hpp
===================================================================
--- src/mpt/io_file/inputfile.hpp	(nonexistent)
+++ src/mpt/io_file/inputfile.hpp	(working copy)
@@ -0,0 +1,213 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_FILE_INPUTFILE_HPP
+#define MPT_IO_FILE_INPUTFILE_HPP
+
+
+
+#include "mpt/base/namespace.hpp"
+#include "mpt/io/io.hpp"
+#include "mpt/io/io_stdstream.hpp"
+#include "mpt/io_file/fstream.hpp"
+#include "mpt/path/os_path.hpp"
+
+#include <ios>
+#include <istream>
+#include <variant>
+#include <vector>
+
+#include <cassert>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+class UncachedInputFile {
+
+private:
+	mpt::os_path m_Filename;
+	mpt::IO::ifstream m_File;
+	bool m_IsValid;
+
+public:
+	UncachedInputFile(const mpt::os_path & filename)
+		: m_Filename(filename)
+		, m_File(m_Filename, std::ios::binary | std::ios::in)
+		, m_IsValid(true) {
+		assert(!m_Filename.empty());
+	}
+
+	~UncachedInputFile() = default;
+
+	bool IsValid() const {
+		return m_IsValid && m_File.good();
+	}
+
+	mpt::os_path GetFilename() const {
+		return m_Filename;
+	}
+
+	std::istream & GetStream() {
+		return m_File;
+	}
+
+};
+
+
+
+class CachedInputFile {
+
+private:
+	mpt::os_path m_Filename;
+	mpt::IO::ifstream m_File;
+	bool m_IsValid;
+	bool m_IsCached;
+	std::vector<std::byte> m_Cache;
+
+public:
+	CachedInputFile(const mpt::os_path & filename)
+		: m_Filename(filename)
+		, m_File(m_Filename, std::ios::binary | std::ios::in)
+		, m_IsValid(false)
+		, m_IsCached(false) {
+		assert(!filename.empty());
+		if (mpt::IO::IsReadSeekable(m_File)) {
+			if (!mpt::IO::SeekEnd(m_File)) {
+				m_File.close();
+				return;
+			}
+			mpt::IO::Offset filesize = mpt::IO::TellRead(m_File);
+			if (!mpt::IO::SeekBegin(m_File)) {
+				m_File.close();
+				return;
+			}
+			if (mpt::in_range<std::size_t>(filesize)) {
+				std::size_t buffersize = mpt::saturate_cast<std::size_t>(filesize);
+				m_Cache.resize(buffersize);
+				if (mpt::IO::ReadRaw(m_File, mpt::as_span(m_Cache)).size() != mpt::saturate_cast<std::size_t>(filesize)) {
+					m_File.close();
+					return;
+				}
+				if (!mpt::IO::SeekBegin(m_File)) {
+					m_File.close();
+					return;
+				}
+				m_IsCached = true;
+				m_IsValid = true;
+				return;
+			}
+		}
+		m_IsValid = true;
+		return;
+	}
+
+	~CachedInputFile() = default;
+
+	bool IsValid() const {
+		return m_IsValid && m_File.good();
+	}
+
+	bool IsCached() const {
+		return m_IsCached;
+	}
+
+	mpt::os_path  GetFilename() const {
+		return m_Filename;
+	}
+
+	std::istream & GetStream() {
+		assert(!m_IsCached);
+		return m_File;
+	}
+
+	mpt::const_byte_span GetCache() {
+		assert(m_IsCached);
+		return mpt::as_span(m_Cache);
+	}
+
+};
+
+
+
+class InputFile {
+
+private:
+	std::variant<std::monostate, UncachedInputFile, CachedInputFile> m_impl;
+
+public:
+	InputFile(const mpt::os_path & filename, bool enable_cache = false) {
+		if (enable_cache) {
+			m_impl.emplace<CachedInputFile>(filename);
+		} else {
+			m_impl.emplace<UncachedInputFile>(filename);
+		}
+	}
+	
+	~InputFile() = default;
+
+	bool IsValid() const {
+		if (std::holds_alternative<UncachedInputFile>(m_impl)) {
+			return std::get<UncachedInputFile>(m_impl).IsValid();
+		} else if (std::holds_alternative<CachedInputFile>(m_impl)) {
+			return std::get<CachedInputFile>(m_impl).IsValid();
+		}
+		return false;
+	}
+
+	bool IsCached() const {
+		if (std::holds_alternative<UncachedInputFile>(m_impl)) {
+			return false;
+		} else if (std::holds_alternative<CachedInputFile>(m_impl)) {
+			return std::get<CachedInputFile>(m_impl).IsCached();
+		}
+		throw false;
+	}
+
+	mpt::os_path GetFilename() const {
+		if (std::holds_alternative<UncachedInputFile>(m_impl)) {
+			return std::get<UncachedInputFile>(m_impl).GetFilename();
+		} else if (std::holds_alternative<CachedInputFile>(m_impl)) {
+			return std::get<CachedInputFile>(m_impl).GetFilename();
+		}
+		return {};
+	}
+
+	std::istream & GetStream() {
+		if (std::holds_alternative<UncachedInputFile>(m_impl)) {
+			return std::get<UncachedInputFile>(m_impl).GetStream();
+		} else if (std::holds_alternative<CachedInputFile>(m_impl)) {
+			return std::get<CachedInputFile>(m_impl).GetStream();
+		}
+		throw std::bad_variant_access();
+	}
+
+	mpt::const_byte_span GetCache() {
+		if (std::holds_alternative<UncachedInputFile>(m_impl)) {
+			throw std::bad_variant_access();
+		} else if (std::holds_alternative<CachedInputFile>(m_impl)) {
+			return std::get<CachedInputFile>(m_impl).GetCache();
+		}
+		throw std::bad_variant_access();
+	}
+
+};
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_FILE_INPUTFILE_HPP

Property changes on: src/mpt/io_file/inputfile.hpp
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/x-c++hdr
\ No newline at end of property
Index: src/mpt/io_file/inputfile_filecursor.hpp
===================================================================
--- src/mpt/io_file/inputfile_filecursor.hpp	(nonexistent)
+++ src/mpt/io_file/inputfile_filecursor.hpp	(working copy)
@@ -0,0 +1,80 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_FILE_INPUTFILE_FILECURSOR_HPP
+#define MPT_IO_FILE_INPUTFILE_FILECURSOR_HPP
+
+
+
+#include "mpt/base/namespace.hpp"
+#include "mpt/io/io.hpp"
+#include "mpt/io/io_stdstream.hpp"
+#include "mpt/io_file/inputfile.hpp"
+#include "mpt/io_read/filecursor_filename_traits.hpp"
+#include "mpt/io_read/filecursor_memory.hpp"
+#include "mpt/io_read/filecursor_stdstream.hpp"
+#include "mpt/io_read/filecursor_traits_filedata.hpp"
+#include "mpt/io_read/filedata_memory.hpp"
+#include "mpt/io_read/filedata_stdstream.hpp"
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+// templated in order to reduce header inter-dependencies
+template <typename Tpath>
+inline FileCursor<FileCursorTraitsFileData, FileCursorFilenameTraits<Tpath>> make_FileCursor(UncachedInputFile & file) {
+	if (!file.IsValid()) {
+		return FileCursor<FileCursorTraitsFileData, FileCursorFilenameTraits<Tpath>>();
+	}
+	return mpt::IO::make_FileCursor<Tpath>(file.GetStream(), std::make_shared<Tpath>(file.GetFilename()));
+}
+
+
+
+// templated in order to reduce header inter-dependencies
+template <typename Tpath>
+inline FileCursor<FileCursorTraitsFileData, FileCursorFilenameTraits<Tpath>> make_FileCursor(CachedInputFile & file) {
+	if (!file.IsValid()) {
+		return FileCursor<FileCursorTraitsFileData, FileCursorFilenameTraits<Tpath>>();
+	}
+	if (file.IsCached()) {
+		return mpt::IO::make_FileCursor<Tpath>(file.GetCache(), std::make_shared<Tpath>(file.GetFilename()));
+	} else {
+		return mpt::IO::make_FileCursor<Tpath>(file.GetStream(), std::make_shared<Tpath>(file.GetFilename()));
+	}
+}
+
+
+
+// templated in order to reduce header inter-dependencies
+template <typename Tpath>
+inline FileCursor<FileCursorTraitsFileData, FileCursorFilenameTraits<Tpath>> make_FileCursor(InputFile & file) {
+	if (!file.IsValid()) {
+		return FileCursor<FileCursorTraitsFileData, FileCursorFilenameTraits<Tpath>>();
+	}
+	if (file.IsCached()) {
+		return mpt::IO::make_FileCursor<Tpath>(file.GetCache(), std::make_shared<Tpath>(file.GetFilename()));
+	} else {
+		return mpt::IO::make_FileCursor<Tpath>(file.GetStream(), std::make_shared<Tpath>(file.GetFilename()));
+	}
+}
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_FILE_INPUTFILE_FILECURSOR_HPP

Property changes on: src/mpt/io_file/inputfile_filecursor.hpp
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/x-c++hdr
\ No newline at end of property
Index: src/mpt/io_file/outputfile.hpp
===================================================================
--- src/mpt/io_file/outputfile.hpp	(nonexistent)
+++ src/mpt/io_file/outputfile.hpp	(working copy)
@@ -0,0 +1,264 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_FILE_OUTPUTFILE_HPP
+#define MPT_IO_FILE_OUTPUTFILE_HPP
+
+
+
+#include "mpt/base/namespace.hpp"
+#include "mpt/io/io.hpp"
+#include "mpt/io/io_stdstream.hpp"
+#include "mpt/io_file/fstream.hpp"
+#include "mpt/path/os_path.hpp"
+#include "mpt/string/types.hpp"
+
+#include <exception>
+#include <ios>
+#include <vector>
+
+#if MPT_LIBCXX_MS
+#include <cstdio>
+#endif // MPT_LIBCXX_MS
+
+#if MPT_LIBCXX_MS
+#include <tchar.h>
+#endif // MPT_LIBCXX_MS
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+enum class FlushMode
+{
+	None = 0,   // no explicit flushes at all
+	Single = 1, // explicitly flush higher-leverl API layers
+	Full = 2,   // explicitly flush *all* layers, up to and including disk write caches
+};
+
+inline FlushMode FlushModeFromBool(bool flush) {
+	return flush ? FlushMode::Full : FlushMode::None;
+}
+
+class SafeOutputFile {
+
+private:
+	FlushMode m_FlushMode;
+#if MPT_LIBCXX_MS
+	std::FILE * m_f = nullptr;
+#else // !MPT_LIBCXX_MS
+	mpt::IO::ofstream m_s;
+#endif // MPT_LIBCXX_MS
+#if MPT_LIBCXX_MS
+	class FILEostream
+		: public mpt::IO::ofstream {
+	public:
+		FILEostream(std::FILE * file)
+			: mpt::IO::ofstream(file) {
+			return;
+		}
+	};
+
+	FILEostream m_s;
+
+	static mpt::tstring convert_mode(std::ios_base::openmode mode, FlushMode flushMode) {
+		mpt::tstring fopen_mode;
+		switch (mode & ~(std::ios_base::ate | std::ios_base::binary)) {
+			case std::ios_base::in:
+				fopen_mode = _T("r");
+				break;
+			case std::ios_base::out:
+				[[fallthrough]];
+			case std::ios_base::out | std::ios_base::trunc:
+				fopen_mode = _T("w");
+				break;
+			case std::ios_base::app:
+				[[fallthrough]];
+			case std::ios_base::out | std::ios_base::app:
+				fopen_mode = _T("a");
+				break;
+			case std::ios_base::out | std::ios_base::in:
+				fopen_mode = _T("r+");
+				break;
+			case std::ios_base::out | std::ios_base::in | std::ios_base::trunc:
+				fopen_mode = _T("w+");
+				break;
+			case std::ios_base::out | std::ios_base::in | std::ios_base::app:
+				[[fallthrough]];
+			case std::ios_base::in | std::ios_base::app:
+				fopen_mode = _T("a+");
+				break;
+		}
+		if (fopen_mode.empty()) {
+			return fopen_mode;
+		}
+		if (mode & std::ios_base::binary) {
+			fopen_mode += _T("b");
+		}
+		if (flushMode == FlushMode::Full) {
+			fopen_mode += _T("c"); // force commit on fflush (MSVC specific)
+		}
+		return fopen_mode;
+	}
+
+	std::FILE * internal_fopen(const mpt::os_path & filename, std::ios_base::openmode mode, FlushMode flushMode) {
+		m_f = nullptr;
+		mpt::tstring fopen_mode = convert_mode(mode, flushMode);
+		if (fopen_mode.empty()) {
+			return nullptr;
+		}
+		std::FILE * f =
+#ifdef UNICODE
+			_wfopen(mpt::support_long_path(mpt::transcode<mpt::tstring>(filename)).c_str(), fopen_mode.c_str())
+#else
+			std::fopen(mpt::support_long_path(mpt::transcode<mpt::tstring>(filename)).c_str(), fopen_mode.c_str())
+#endif
+			;
+		if (!f) {
+			return nullptr;
+		}
+		if (mode & std::ios_base::ate) {
+			if (std::fseek(f, 0, SEEK_END) != 0) {
+				std::fclose(f);
+				f = nullptr;
+				return nullptr;
+			}
+		}
+		m_f = f;
+		return f;
+	}
+
+#endif // MPT_LIBCXX_MS
+
+public:
+	SafeOutputFile() = delete;
+
+	explicit SafeOutputFile(const mpt::os_path & filename, std::ios_base::openmode mode = std::ios_base::out, FlushMode flushMode = FlushMode::Full)
+		: m_FlushMode(flushMode)
+#if MPT_LIBCXX_MS
+		, m_s(internal_fopen(filename, mode | std::ios_base::out, flushMode))
+#else // !MPT_LIBCXX_MS
+		, m_s(filename, mode)
+#endif // MPT_LIBCXX_MS
+	{
+		if (!stream().is_open()) {
+			stream().setstate(mpt::IO::ofstream::failbit);
+		}
+	}
+
+	mpt::IO::ofstream & stream() {
+		return m_s;
+	}
+
+	operator mpt::IO::ofstream &() {
+		return stream();
+	}
+
+	const mpt::IO::ofstream & stream() const {
+		return m_s;
+	}
+
+	operator const mpt::IO::ofstream &() const {
+		return stream();
+	}
+
+	operator bool() const {
+		return stream() ? true : false;
+	}
+
+	bool operator!() const {
+		return stream().operator!();
+	}
+
+	// cppcheck-suppress exceptThrowInDestructor
+	~SafeOutputFile() noexcept(false) {
+		const bool mayThrow = (std::uncaught_exceptions() == 0);
+		if (!stream()) {
+#if MPT_LIBCXX_MS
+			if (m_f) {
+				std::fclose(m_f);
+			}
+#endif // MPT_LIBCXX_MS
+			if (mayThrow && (stream().exceptions() & (std::ios::badbit | std::ios::failbit))) {
+				// cppcheck-suppress exceptThrowInDestructor
+				throw std::ios_base::failure(std::string("Error before flushing file buffers."));
+			}
+			return;
+		}
+		if (!stream().rdbuf()) {
+#if MPT_LIBCXX_MS
+			if (m_f) {
+				std::fclose(m_f);
+			}
+#endif // MPT_LIBCXX_MS
+			if (mayThrow && (stream().exceptions() & (std::ios::badbit | std::ios::failbit))) {
+				// cppcheck-suppress exceptThrowInDestructor
+				throw std::ios_base::failure(std::string("Error before flushing file buffers."));
+			}
+			return;
+		}
+#if MPT_LIBCXX_MS
+		if (!m_f) {
+			return;
+		}
+#endif // MPT_LIBCXX_MS
+		bool errorOnFlush = false;
+		if (m_FlushMode != FlushMode::None) {
+			try {
+				if (stream().rdbuf()->pubsync() != 0) {
+					errorOnFlush = true;
+				}
+			} catch (const std::exception &) {
+				errorOnFlush = true;
+#if MPT_LIBCXX_MS
+				if (m_FlushMode != FlushMode::None) {
+					if (std::fflush(m_f) != 0) {
+						errorOnFlush = true;
+					}
+				}
+				if (std::fclose(m_f) != 0) {
+					errorOnFlush = true;
+				}
+#endif // MPT_LIBCXX_MS
+				if (mayThrow) {
+					// ignore errorOnFlush here, and re-throw the earlier exception
+					// cppcheck-suppress exceptThrowInDestructor
+					throw;
+				}
+			}
+		}
+#if MPT_LIBCXX_MS
+		if (m_FlushMode != FlushMode::None) {
+			if (std::fflush(m_f) != 0) {
+				errorOnFlush = true;
+			}
+		}
+		if (std::fclose(m_f) != 0) {
+			errorOnFlush = true;
+		}
+#endif // MPT_LIBCXX_MS
+		if (mayThrow && errorOnFlush && (stream().exceptions() & (std::ios::badbit | std::ios::failbit))) {
+			// cppcheck-suppress exceptThrowInDestructor
+			throw std::ios_base::failure(std::string("Error flushing file buffers."));
+		}
+	}
+};
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_FILE_OUTPUTFILE_HPP

Property changes on: src/mpt/io_file/outputfile.hpp
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/x-c++hdr
\ No newline at end of property
Index: src/mpt/io_file/unique_basename.hpp
===================================================================
--- src/mpt/io_file/unique_basename.hpp	(nonexistent)
+++ src/mpt/io_file/unique_basename.hpp	(working copy)
@@ -0,0 +1,95 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_FILE_UNIQUE_BASENAME_HPP
+#define MPT_IO_FILE_UNIQUE_BASENAME_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/path/os_path.hpp"
+#include "mpt/random/default_engines.hpp"
+#include "mpt/random/device.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+#include "mpt/uuid/uuid.hpp"
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+class unique_basename {
+
+private:
+	mpt::os_path m_Basename;
+
+private:
+	static mpt::good_engine make_prng() {
+		mpt::sane_random_device rd;
+		return mpt::make_prng<mpt::good_engine>(rd);
+	}
+
+	static mpt::UUID make_uuid() {
+		mpt::good_engine rng = make_prng();
+		return mpt::UUID::Generate(rng);
+	}
+
+public:
+	explicit unique_basename(mpt::UUID uuid)
+		: m_Basename(mpt::transcode<mpt::os_path>(uuid.ToUString()))
+	{
+		return;
+	}
+
+	explicit unique_basename(const mpt::os_path & prefix, mpt::UUID uuid)
+		: m_Basename(prefix + mpt::transcode<mpt::os_path>(uuid.ToUString()))
+	{
+		return;
+	}
+
+	template <typename Trng>
+	explicit unique_basename(Trng & rng)
+		: m_Basename(mpt::transcode<mpt::os_path>(mpt::UUID::Generate(rng).ToUString()))
+	{
+		return;
+	}
+
+	template <typename Trng>
+	explicit unique_basename(const mpt::os_path & prefix, Trng & rng)
+		: m_Basename(prefix + mpt::transcode<mpt::os_path>(mpt::UUID::Generate(rng).ToUString()))
+	{
+		return;
+	}
+
+	explicit unique_basename()
+		: m_Basename(mpt::transcode<mpt::os_path>(make_uuid().ToUString()))
+	{
+		return;
+	}
+
+public:
+	operator mpt::os_path() const {
+		return m_Basename;
+	}
+
+}; // class unique_basename
+
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_FILE_UNIQUE_BASENAME_HPP

Property changes on: src/mpt/io_file/unique_basename.hpp
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/x-c++hdr
\ No newline at end of property
Index: src/mpt/io_file/unique_tempfilename.hpp
===================================================================
--- src/mpt/io_file/unique_tempfilename.hpp	(nonexistent)
+++ src/mpt/io_file/unique_tempfilename.hpp	(working copy)
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_FILE_UNIQUE_TEMPFILENAME_HPP
+#define MPT_IO_FILE_UNIQUE_TEMPFILENAME_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/io_file/unique_basename.hpp"
+#include "mpt/fs/fs.hpp"
+#include "mpt/path/native_path.hpp"
+#include "mpt/path/os_path.hpp"
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+class unique_tempfilename {
+
+private:
+	mpt::os_path m_Filename;
+
+public:
+	unique_tempfilename(const unique_basename & basename, const mpt::os_path & suffix = MPT_OS_PATH(".tmp"))
+		: m_Filename(mpt::fs<mpt::native_path>{}.get_temp_directory().WithTrailingSlash() + static_cast<mpt::os_path>(basename) + suffix)
+	{
+		return;
+	}
+
+public:
+	operator mpt::os_path() const {
+		return m_Filename;
+	}
+
+}; // class unique_tempfilename
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_FILE_UNIQUE_TEMPFILENAME_HPP

Property changes on: src/mpt/io_file/unique_tempfilename.hpp
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/x-c++hdr
\ No newline at end of property
Index: src/mpt/io_file/fileadapter.hpp
===================================================================
--- src/mpt/io_file/fileadapter.hpp	(nonexistent)
+++ src/mpt/io_file/fileadapter.hpp	(working copy)
@@ -0,0 +1,182 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_FILE_FILEADAPTER_HPP
+#define MPT_IO_FILE_FILEADAPTER_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/saturate_cast.hpp"
+#include "mpt/io/base.hpp"
+#if !MPT_OS_WINDOWS
+#include "mpt/io/io.hpp"
+#include "mpt/io/io_stdstream.hpp"
+#include "mpt/io_file/fstream.hpp"
+#endif // !MPT_OS_WINDOWS
+#include "mpt/io_file/unique_basename.hpp"
+#include "mpt/io_file/unique_tempfilename.hpp"
+#if MPT_OS_WINDOWS
+#include "mpt/path/os_path.hpp"
+#endif // MPT_OS_WINDOWS
+#include "mpt/string_transcode/transcode.hpp"
+#if MPT_OS_WINDOWS
+#include "mpt/system_error/system_error.hpp"
+#endif // MPT_OS_WINDOWS
+
+#include <stdexcept>
+#if !MPT_OS_WINDOWS
+#include <system_error>
+#endif // MPT_OS_WINDOWS
+
+#include <cstddef>
+#if !MPT_OS_WINDOWS
+#include <cstdio>
+#endif // !MPT_OS_WINDOWS
+
+#if MPT_OS_WINDOWS
+#include <windows.h>
+#endif // MPT_OS_WINDOWS
+
+#if MPT_OS_WINDOWS
+#if MPT_OS_WINDOWS_WINRT
+#if (_WIN32_WINNT >= 0x0602)
+#define MPT_IO_FILE_ONDISKFILEWRAPPER_USE_CREATEFILE
+#endif
+#else // !MPT_OS_WINDOWS_WINRT
+#define MPT_IO_FILE_ONDISKFILEWRAPPER_USE_CREATEFILE
+#endif // MPT_OS_WINDOWS_WINRT
+#endif // MPT_OS_WINDOWS
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+template <typename TFileCursor>
+class FileAdapter {
+
+private:
+	mpt::os_path m_Filename;
+	bool m_IsTempFile;
+
+public:
+	FileAdapter(TFileCursor & file, const mpt::os_path & tempName = unique_tempfilename{unique_basename{}, MPT_OS_PATH(".tmp")})
+		: m_IsTempFile(false) {
+		try {
+			file.Rewind();
+			if (!file.GetOptionalFileName()) {
+
+#ifdef MPT_IO_FILE_ONDISKFILEWRAPPER_USE_CREATEFILE
+
+				HANDLE hFile = NULL;
+#if MPT_OS_WINDOWS_WINRT
+				hFile = mpt::windows::CheckFileHANDLE(CreateFile2(mpt::support_long_path(tempName).c_str(), GENERIC_WRITE, FILE_SHARE_READ, CREATE_ALWAYS, NULL));
+#else
+				hFile = mpt::windows::CheckFileHANDLE(CreateFile(mpt::support_long_path(tempName).c_str(), GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL));
+#endif
+				while (!file.EndOfFile()) {
+					typename TFileCursor::PinnedView view = file.ReadPinnedView(mpt::IO::BUFFERSIZE_NORMAL);
+					std::size_t towrite = view.size();
+					std::size_t written = 0;
+					do {
+						DWORD chunkSize = mpt::saturate_cast<DWORD>(towrite);
+						DWORD chunkDone = 0;
+						try {
+							mpt::windows::CheckBOOL(WriteFile(hFile, view.data() + written, chunkSize, &chunkDone, NULL));
+						} catch (...) {
+							CloseHandle(hFile);
+							hFile = NULL;
+							throw;
+						}
+						if (chunkDone != chunkSize) {
+							CloseHandle(hFile);
+							hFile = NULL;
+							throw std::runtime_error("Incomplete WriteFile().");
+						}
+						towrite -= chunkDone;
+						written += chunkDone;
+					} while (towrite > 0);
+				}
+				CloseHandle(hFile);
+				hFile = NULL;
+
+#else // !MPT_IO_FILE_ONDISKFILEWRAPPER_USE_CREATEFILE
+
+				mpt::IO::ofstream f(tempName, std::ios::binary);
+				if (!f) {
+					throw std::runtime_error("Error creating temporary file.");
+				}
+				while (!file.EndOfFile()) {
+					typename TFileCursor::PinnedView view = file.ReadPinnedView(mpt::IO::BUFFERSIZE_NORMAL);
+					std::size_t towrite = view.size();
+					std::size_t written = 0;
+					do {
+						std::size_t chunkSize = mpt::saturate_cast<std::size_t>(towrite);
+						bool chunkOk = false;
+						chunkOk = mpt::IO::WriteRaw(f, mpt::const_byte_span(view.data() + written, chunkSize));
+						if (!chunkOk) {
+							throw std::runtime_error("Incomplete Write.");
+						}
+						towrite -= chunkSize;
+						written += chunkSize;
+					} while (towrite > 0);
+				}
+				f.close();
+
+#endif // MPT_ONDISKFILEWRAPPER_NO_CREATEFILE
+
+				m_Filename = tempName;
+				m_IsTempFile = true;
+			} else {
+				m_Filename = file.GetOptionalFileName().value();
+			}
+		} catch (const std::runtime_error &) {
+			m_Filename = mpt::os_path{};
+			m_IsTempFile = false;
+		}
+	}
+
+	~FileAdapter() {
+		if (m_IsTempFile) {
+#if MPT_OS_WINDOWS
+			DeleteFile(m_Filename.c_str());
+#else // !MPT_OS_WINDOWS
+			std::remove(m_Filename.c_str());
+			//std::error_code ec{};
+			//std::filesystem::remove(mpt::transcode<std::filesystem::path>(m_Filename.AsNative()), ec);
+#endif // MPT_OS_WINDOWS
+			m_IsTempFile = false;
+		}
+		m_Filename = mpt::os_path{};
+	}
+
+public:
+	bool IsValid() const {
+		return !m_Filename.empty();
+	}
+
+	mpt::os_path GetFilename() const {
+		return m_Filename;
+	}
+
+}; // class FileAdapter
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_FILE_FILEADAPTER_HPP

Property changes on: src/mpt/io_file/fileadapter.hpp
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/x-c++hdr
\ No newline at end of property
Index: src/mpt/io_file/fileref.hpp
===================================================================
--- src/mpt/io_file/fileref.hpp	(nonexistent)
+++ src/mpt/io_file/fileref.hpp	(working copy)
@@ -0,0 +1,124 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_FILE_FILEREF_HPP
+#define MPT_IO_FILE_FILEREF_HPP
+
+
+
+#include "mpt/base/memory.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/span.hpp"
+#include "mpt/io/base.hpp"
+#include "mpt/io/io.hpp"
+#include "mpt/io/io_stdstream.hpp"
+#include "mpt/io_file/fstream.hpp"
+#include "mpt/path/os_path.hpp"
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+// FileRef is a simple reference to an on-disk file by the means of a
+// filename which allows easy assignment of the whole file contents to and from
+// byte buffers.
+
+class FileRef {
+private:
+	const mpt::os_path m_Filename;
+
+public:
+	FileRef(const mpt::os_path & filename)
+		: m_Filename(filename) {
+		return;
+	}
+
+public:
+	FileRef & operator=(const std::vector<std::byte> & data) {
+		mpt::IO::ofstream file(m_Filename, std::ios::binary);
+		file.exceptions(std::ios_base::failbit | std::ios_base::badbit);
+		mpt::IO::WriteRaw(file, mpt::as_span(data));
+		mpt::IO::Flush(file);
+		return *this;
+	}
+
+	FileRef & operator=(const std::vector<char> & data) {
+		mpt::IO::ofstream file(m_Filename, std::ios::binary);
+		file.exceptions(std::ios_base::failbit | std::ios_base::badbit);
+		mpt::IO::WriteRaw(file, mpt::as_span(data));
+		mpt::IO::Flush(file);
+		return *this;
+	}
+
+	FileRef & operator=(const std::string & data) {
+		mpt::IO::ofstream file(m_Filename, std::ios::binary);
+		file.exceptions(std::ios_base::failbit | std::ios_base::badbit);
+		mpt::IO::WriteRaw(file, mpt::as_span(data));
+		mpt::IO::Flush(file);
+		return *this;
+	}
+
+	operator std::vector<std::byte>() const {
+		mpt::IO::ifstream file(m_Filename, std::ios::binary);
+		if (!mpt::IO::IsValid(file)) {
+			return std::vector<std::byte>();
+		}
+		file.exceptions(std::ios_base::failbit | std::ios_base::badbit);
+		mpt::IO::SeekEnd(file);
+		std::vector<std::byte> buf(mpt::saturate_cast<std::size_t>(mpt::IO::TellRead(file)));
+		mpt::IO::SeekBegin(file);
+		mpt::IO::ReadRaw(file, mpt::as_span(buf));
+		return buf;
+	}
+
+	operator std::vector<char>() const {
+		mpt::IO::ifstream file(m_Filename, std::ios::binary);
+		if (!mpt::IO::IsValid(file)) {
+			return std::vector<char>();
+		}
+		file.exceptions(std::ios_base::failbit | std::ios_base::badbit);
+		mpt::IO::SeekEnd(file);
+		std::vector<char> buf(mpt::saturate_cast<std::size_t>(mpt::IO::TellRead(file)));
+		mpt::IO::SeekBegin(file);
+		mpt::IO::ReadRaw(file, mpt::as_span(buf));
+		return buf;
+	}
+
+	operator std::string() const {
+		mpt::IO::ifstream file(m_Filename, std::ios::binary);
+		if (!mpt::IO::IsValid(file)) {
+			return std::string();
+		}
+		file.exceptions(std::ios_base::failbit | std::ios_base::badbit);
+		mpt::IO::SeekEnd(file);
+		std::vector<char> buf(mpt::saturate_cast<std::size_t>(mpt::IO::TellRead(file)));
+		mpt::IO::SeekBegin(file);
+		mpt::IO::ReadRaw(file, mpt::as_span(buf));
+		return mpt::buffer_cast<std::string>(buf);
+	}
+};
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_FILE_FILEREF_HPP

Property changes on: src/mpt/io_file/fileref.hpp
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/x-c++hdr
\ No newline at end of property
Index: src/mpt/io_file/fstream.hpp
===================================================================
--- src/mpt/io_file/fstream.hpp	(nonexistent)
+++ src/mpt/io_file/fstream.hpp	(working copy)
@@ -0,0 +1,156 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_FILE_FSTREAM_HPP
+#define MPT_IO_FILE_FSTREAM_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/span.hpp"
+#include "mpt/io/base.hpp"
+#include "mpt/io/io.hpp"
+#include "mpt/io/io_stdstream.hpp"
+#include "mpt/io_file/fstream.hpp"
+#include "mpt/path/os_path.hpp"
+#include "mpt/path/os_path_long.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+
+#if defined(MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR)
+#if MPT_GCC_AT_LEAST(9, 1, 0) && !defined(MPT_COMPILER_QUIRK_NO_FILESYSTEM)
+#include <filesystem>
+#endif // MPT_GCC_AT_LEAST(9,1,0) && !defined(MPT_COMPILER_QUIRK_NO_FILESYSTEM)
+#endif // MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR
+#include <fstream>
+#include <ios>
+#include <string>
+#include <string_view>
+
+#if MPT_LIBCXX_MS
+#include <cstdio>
+#endif // MPT_LIBCXX_MS
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+namespace detail {
+
+template<typename Tbase>
+inline void fstream_open(Tbase & base, const mpt::os_path & filename, std::ios_base::openmode mode) {
+#if defined(MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR)
+#if MPT_GCC_AT_LEAST(9, 1, 0) && !defined(MPT_COMPILER_QUIRK_NO_FILESYSTEM)
+	base.open(static_cast<std::filesystem::path>(filename.AsNative()), mode);
+#else // !MPT_GCC_AT_LEAST(9,1,0) || MPT_COMPILER_QUIRK_NO_FILESYSTEM
+	// Warning: MinGW with GCC earlier than 9.1 detected. Standard library does neither provide std::fstream wchar_t overloads nor std::filesystem with wchar_t support. Unicode filename support is thus unavailable.
+	base.open(mpt::transcode<std::string>(mpt::logical_encoding::locale, filename.AsNative()).c_str(), mode);
+#endif // MPT_GCC_AT_LEAST(9,1,0) && !MPT_COMPILER_QUIRK_NO_FILESYSTEM
+#else // !MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR
+	base.open(filename.c_str(), mode);
+#endif // MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR
+}
+
+} // namespace detail
+
+// We cannot rely on implicit conversion of mpt::path to std::filesystem::path when constructing std::fstream
+// because of broken overload implementation in GCC libstdc++ 8, 9, 10.
+// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95642
+// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90704
+
+class fstream
+	: public std::fstream {
+private:
+	using base_type = std::fstream;
+
+public:
+	explicit fstream(const mpt::os_path & filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) {
+		detail::fstream_open<base_type>(*this, filename, mode);
+	}
+#if MPT_LIBCXX_MS
+protected:
+	fstream(std::FILE * file)
+		: std::fstream(file) {
+	}
+#endif // MPT_LIBCXX_MS
+public:
+	void open(const char * filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) = delete;
+	void open(const std::string_view & filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) = delete;
+	void open(const std::string & filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) = delete;
+#if MPT_OS_WINDOWS && !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+	void open(const wchar_t * filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) = delete;
+	void open(const std::wstring_view & filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) = delete;
+	void open(const std::wstring & filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) = delete;
+#endif
+};
+
+class ifstream
+	: public std::ifstream {
+private:
+	using base_type = std::ifstream;
+
+public:
+	explicit ifstream(const mpt::os_path & filename, std::ios_base::openmode mode = std::ios_base::in) {
+		detail::fstream_open<base_type>(*this, filename, mode);
+	}
+#if MPT_LIBCXX_MS
+protected:
+	ifstream(std::FILE * file)
+		: std::ifstream(file) {
+	}
+#endif // MPT_LIBCXX_MS
+public:
+	void open(const char * filename, std::ios_base::openmode mode = std::ios_base::in) = delete;
+	void open(const std::string_view & filename, std::ios_base::openmode mode = std::ios_base::in) = delete;
+	void open(const std::string & filename, std::ios_base::openmode mode = std::ios_base::in) = delete;
+#if MPT_OS_WINDOWS && !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+	void open(const wchar_t * filename, std::ios_base::openmode mode = std::ios_base::in) = delete;
+	void open(const std::wstring_view & filename, std::ios_base::openmode mode = std::ios_base::in) = delete;
+	void open(const std::wstring & filename, std::ios_base::openmode mode = std::ios_base::in) = delete;
+#endif
+};
+
+class ofstream
+	: public std::ofstream {
+private:
+	using base_type = std::ofstream;
+
+public:
+	explicit ofstream(const mpt::os_path & filename, std::ios_base::openmode mode = std::ios_base::out) {
+		detail::fstream_open<base_type>(*this, filename, mode);
+	}
+#if MPT_LIBCXX_MS
+protected:
+	ofstream(std::FILE * file)
+		: std::ofstream(file) {
+	}
+#endif // MPT_LIBCXX_MS
+public:
+	void open(const char * filename, std::ios_base::openmode mode = std::ios_base::out) = delete;
+	void open(const std::string_view & filename, std::ios_base::openmode mode = std::ios_base::out) = delete;
+	void open(const std::string & filename, std::ios_base::openmode mode = std::ios_base::out) = delete;
+#if MPT_OS_WINDOWS && !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+	void open(const wchar_t * filename, std::ios_base::openmode mode = std::ios_base::out) = delete;
+	void open(const std::wstring_view & filename, std::ios_base::openmode mode = std::ios_base::out) = delete;
+	void open(const std::wstring & filename, std::ios_base::openmode mode = std::ios_base::out) = delete;
+#endif
+};
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_FILE_FSTREAM_HPP

Property changes on: src/mpt/io_file/fstream.hpp
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/x-c++hdr
\ No newline at end of property
Index: src/mpt/io_file/inputfile.hpp
===================================================================
--- src/mpt/io_file/inputfile.hpp	(nonexistent)
+++ src/mpt/io_file/inputfile.hpp	(working copy)
@@ -0,0 +1,213 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_FILE_INPUTFILE_HPP
+#define MPT_IO_FILE_INPUTFILE_HPP
+
+
+
+#include "mpt/base/namespace.hpp"
+#include "mpt/io/io.hpp"
+#include "mpt/io/io_stdstream.hpp"
+#include "mpt/io_file/fstream.hpp"
+#include "mpt/path/os_path.hpp"
+
+#include <ios>
+#include <istream>
+#include <variant>
+#include <vector>
+
+#include <cassert>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+class UncachedInputFile {
+
+private:
+	mpt::os_path m_Filename;
+	mpt::IO::ifstream m_File;
+	bool m_IsValid;
+
+public:
+	UncachedInputFile(const mpt::os_path & filename)
+		: m_Filename(filename)
+		, m_File(m_Filename, std::ios::binary | std::ios::in)
+		, m_IsValid(true) {
+		assert(!m_Filename.empty());
+	}
+
+	~UncachedInputFile() = default;
+
+	bool IsValid() const {
+		return m_IsValid && m_File.good();
+	}
+
+	mpt::os_path GetFilename() const {
+		return m_Filename;
+	}
+
+	std::istream & GetStream() {
+		return m_File;
+	}
+
+};
+
+
+
+class CachedInputFile {
+
+private:
+	mpt::os_path m_Filename;
+	mpt::IO::ifstream m_File;
+	bool m_IsValid;
+	bool m_IsCached;
+	std::vector<std::byte> m_Cache;
+
+public:
+	CachedInputFile(const mpt::os_path & filename)
+		: m_Filename(filename)
+		, m_File(m_Filename, std::ios::binary | std::ios::in)
+		, m_IsValid(false)
+		, m_IsCached(false) {
+		assert(!filename.empty());
+		if (mpt::IO::IsReadSeekable(m_File)) {
+			if (!mpt::IO::SeekEnd(m_File)) {
+				m_File.close();
+				return;
+			}
+			mpt::IO::Offset filesize = mpt::IO::TellRead(m_File);
+			if (!mpt::IO::SeekBegin(m_File)) {
+				m_File.close();
+				return;
+			}
+			if (mpt::in_range<std::size_t>(filesize)) {
+				std::size_t buffersize = mpt::saturate_cast<std::size_t>(filesize);
+				m_Cache.resize(buffersize);
+				if (mpt::IO::ReadRaw(m_File, mpt::as_span(m_Cache)).size() != mpt::saturate_cast<std::size_t>(filesize)) {
+					m_File.close();
+					return;
+				}
+				if (!mpt::IO::SeekBegin(m_File)) {
+					m_File.close();
+					return;
+				}
+				m_IsCached = true;
+				m_IsValid = true;
+				return;
+			}
+		}
+		m_IsValid = true;
+		return;
+	}
+
+	~CachedInputFile() = default;
+
+	bool IsValid() const {
+		return m_IsValid && m_File.good();
+	}
+
+	bool IsCached() const {
+		return m_IsCached;
+	}
+
+	mpt::os_path  GetFilename() const {
+		return m_Filename;
+	}
+
+	std::istream & GetStream() {
+		assert(!m_IsCached);
+		return m_File;
+	}
+
+	mpt::const_byte_span GetCache() {
+		assert(m_IsCached);
+		return mpt::as_span(m_Cache);
+	}
+
+};
+
+
+
+class InputFile {
+
+private:
+	std::variant<std::monostate, UncachedInputFile, CachedInputFile> m_impl;
+
+public:
+	InputFile(const mpt::os_path & filename, bool enable_cache = false) {
+		if (enable_cache) {
+			m_impl.emplace<CachedInputFile>(filename);
+		} else {
+			m_impl.emplace<UncachedInputFile>(filename);
+		}
+	}
+	
+	~InputFile() = default;
+
+	bool IsValid() const {
+		if (std::holds_alternative<UncachedInputFile>(m_impl)) {
+			return std::get<UncachedInputFile>(m_impl).IsValid();
+		} else if (std::holds_alternative<CachedInputFile>(m_impl)) {
+			return std::get<CachedInputFile>(m_impl).IsValid();
+		}
+		return false;
+	}
+
+	bool IsCached() const {
+		if (std::holds_alternative<UncachedInputFile>(m_impl)) {
+			return false;
+		} else if (std::holds_alternative<CachedInputFile>(m_impl)) {
+			return std::get<CachedInputFile>(m_impl).IsCached();
+		}
+		throw false;
+	}
+
+	mpt::os_path GetFilename() const {
+		if (std::holds_alternative<UncachedInputFile>(m_impl)) {
+			return std::get<UncachedInputFile>(m_impl).GetFilename();
+		} else if (std::holds_alternative<CachedInputFile>(m_impl)) {
+			return std::get<CachedInputFile>(m_impl).GetFilename();
+		}
+		return {};
+	}
+
+	std::istream & GetStream() {
+		if (std::holds_alternative<UncachedInputFile>(m_impl)) {
+			return std::get<UncachedInputFile>(m_impl).GetStream();
+		} else if (std::holds_alternative<CachedInputFile>(m_impl)) {
+			return std::get<CachedInputFile>(m_impl).GetStream();
+		}
+		throw std::bad_variant_access();
+	}
+
+	mpt::const_byte_span GetCache() {
+		if (std::holds_alternative<UncachedInputFile>(m_impl)) {
+			throw std::bad_variant_access();
+		} else if (std::holds_alternative<CachedInputFile>(m_impl)) {
+			return std::get<CachedInputFile>(m_impl).GetCache();
+		}
+		throw std::bad_variant_access();
+	}
+
+};
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_FILE_INPUTFILE_HPP

Property changes on: src/mpt/io_file/inputfile.hpp
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/x-c++hdr
\ No newline at end of property
Index: src/mpt/io_file/inputfile_filecursor.hpp
===================================================================
--- src/mpt/io_file/inputfile_filecursor.hpp	(nonexistent)
+++ src/mpt/io_file/inputfile_filecursor.hpp	(working copy)
@@ -0,0 +1,80 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_FILE_INPUTFILE_FILECURSOR_HPP
+#define MPT_IO_FILE_INPUTFILE_FILECURSOR_HPP
+
+
+
+#include "mpt/base/namespace.hpp"
+#include "mpt/io/io.hpp"
+#include "mpt/io/io_stdstream.hpp"
+#include "mpt/io_file/inputfile.hpp"
+#include "mpt/io_read/filecursor_filename_traits.hpp"
+#include "mpt/io_read/filecursor_memory.hpp"
+#include "mpt/io_read/filecursor_stdstream.hpp"
+#include "mpt/io_read/filecursor_traits_filedata.hpp"
+#include "mpt/io_read/filedata_memory.hpp"
+#include "mpt/io_read/filedata_stdstream.hpp"
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+// templated in order to reduce header inter-dependencies
+template <typename Tpath>
+inline FileCursor<FileCursorTraitsFileData, FileCursorFilenameTraits<Tpath>> make_FileCursor(UncachedInputFile & file) {
+	if (!file.IsValid()) {
+		return FileCursor<FileCursorTraitsFileData, FileCursorFilenameTraits<Tpath>>();
+	}
+	return mpt::IO::make_FileCursor<Tpath>(file.GetStream(), std::make_shared<Tpath>(file.GetFilename()));
+}
+
+
+
+// templated in order to reduce header inter-dependencies
+template <typename Tpath>
+inline FileCursor<FileCursorTraitsFileData, FileCursorFilenameTraits<Tpath>> make_FileCursor(CachedInputFile & file) {
+	if (!file.IsValid()) {
+		return FileCursor<FileCursorTraitsFileData, FileCursorFilenameTraits<Tpath>>();
+	}
+	if (file.IsCached()) {
+		return mpt::IO::make_FileCursor<Tpath>(file.GetCache(), std::make_shared<Tpath>(file.GetFilename()));
+	} else {
+		return mpt::IO::make_FileCursor<Tpath>(file.GetStream(), std::make_shared<Tpath>(file.GetFilename()));
+	}
+}
+
+
+
+// templated in order to reduce header inter-dependencies
+template <typename Tpath>
+inline FileCursor<FileCursorTraitsFileData, FileCursorFilenameTraits<Tpath>> make_FileCursor(InputFile & file) {
+	if (!file.IsValid()) {
+		return FileCursor<FileCursorTraitsFileData, FileCursorFilenameTraits<Tpath>>();
+	}
+	if (file.IsCached()) {
+		return mpt::IO::make_FileCursor<Tpath>(file.GetCache(), std::make_shared<Tpath>(file.GetFilename()));
+	} else {
+		return mpt::IO::make_FileCursor<Tpath>(file.GetStream(), std::make_shared<Tpath>(file.GetFilename()));
+	}
+}
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_FILE_INPUTFILE_FILECURSOR_HPP

Property changes on: src/mpt/io_file/inputfile_filecursor.hpp
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/x-c++hdr
\ No newline at end of property
Index: src/mpt/io_file/outputfile.hpp
===================================================================
--- src/mpt/io_file/outputfile.hpp	(nonexistent)
+++ src/mpt/io_file/outputfile.hpp	(working copy)
@@ -0,0 +1,264 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_FILE_OUTPUTFILE_HPP
+#define MPT_IO_FILE_OUTPUTFILE_HPP
+
+
+
+#include "mpt/base/namespace.hpp"
+#include "mpt/io/io.hpp"
+#include "mpt/io/io_stdstream.hpp"
+#include "mpt/io_file/fstream.hpp"
+#include "mpt/path/os_path.hpp"
+#include "mpt/string/types.hpp"
+
+#include <exception>
+#include <ios>
+#include <vector>
+
+#if MPT_LIBCXX_MS
+#include <cstdio>
+#endif // MPT_LIBCXX_MS
+
+#if MPT_LIBCXX_MS
+#include <tchar.h>
+#endif // MPT_LIBCXX_MS
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+enum class FlushMode
+{
+	None = 0,   // no explicit flushes at all
+	Single = 1, // explicitly flush higher-leverl API layers
+	Full = 2,   // explicitly flush *all* layers, up to and including disk write caches
+};
+
+inline FlushMode FlushModeFromBool(bool flush) {
+	return flush ? FlushMode::Full : FlushMode::None;
+}
+
+class SafeOutputFile {
+
+private:
+	FlushMode m_FlushMode;
+#if MPT_LIBCXX_MS
+	std::FILE * m_f = nullptr;
+#else // !MPT_LIBCXX_MS
+	mpt::IO::ofstream m_s;
+#endif // MPT_LIBCXX_MS
+#if MPT_LIBCXX_MS
+	class FILEostream
+		: public mpt::IO::ofstream {
+	public:
+		FILEostream(std::FILE * file)
+			: mpt::IO::ofstream(file) {
+			return;
+		}
+	};
+
+	FILEostream m_s;
+
+	static mpt::tstring convert_mode(std::ios_base::openmode mode, FlushMode flushMode) {
+		mpt::tstring fopen_mode;
+		switch (mode & ~(std::ios_base::ate | std::ios_base::binary)) {
+			case std::ios_base::in:
+				fopen_mode = _T("r");
+				break;
+			case std::ios_base::out:
+				[[fallthrough]];
+			case std::ios_base::out | std::ios_base::trunc:
+				fopen_mode = _T("w");
+				break;
+			case std::ios_base::app:
+				[[fallthrough]];
+			case std::ios_base::out | std::ios_base::app:
+				fopen_mode = _T("a");
+				break;
+			case std::ios_base::out | std::ios_base::in:
+				fopen_mode = _T("r+");
+				break;
+			case std::ios_base::out | std::ios_base::in | std::ios_base::trunc:
+				fopen_mode = _T("w+");
+				break;
+			case std::ios_base::out | std::ios_base::in | std::ios_base::app:
+				[[fallthrough]];
+			case std::ios_base::in | std::ios_base::app:
+				fopen_mode = _T("a+");
+				break;
+		}
+		if (fopen_mode.empty()) {
+			return fopen_mode;
+		}
+		if (mode & std::ios_base::binary) {
+			fopen_mode += _T("b");
+		}
+		if (flushMode == FlushMode::Full) {
+			fopen_mode += _T("c"); // force commit on fflush (MSVC specific)
+		}
+		return fopen_mode;
+	}
+
+	std::FILE * internal_fopen(const mpt::os_path & filename, std::ios_base::openmode mode, FlushMode flushMode) {
+		m_f = nullptr;
+		mpt::tstring fopen_mode = convert_mode(mode, flushMode);
+		if (fopen_mode.empty()) {
+			return nullptr;
+		}
+		std::FILE * f =
+#ifdef UNICODE
+			_wfopen(mpt::support_long_path(mpt::transcode<mpt::tstring>(filename)).c_str(), fopen_mode.c_str())
+#else
+			std::fopen(mpt::support_long_path(mpt::transcode<mpt::tstring>(filename)).c_str(), fopen_mode.c_str())
+#endif
+			;
+		if (!f) {
+			return nullptr;
+		}
+		if (mode & std::ios_base::ate) {
+			if (std::fseek(f, 0, SEEK_END) != 0) {
+				std::fclose(f);
+				f = nullptr;
+				return nullptr;
+			}
+		}
+		m_f = f;
+		return f;
+	}
+
+#endif // MPT_LIBCXX_MS
+
+public:
+	SafeOutputFile() = delete;
+
+	explicit SafeOutputFile(const mpt::os_path & filename, std::ios_base::openmode mode = std::ios_base::out, FlushMode flushMode = FlushMode::Full)
+		: m_FlushMode(flushMode)
+#if MPT_LIBCXX_MS
+		, m_s(internal_fopen(filename, mode | std::ios_base::out, flushMode))
+#else // !MPT_LIBCXX_MS
+		, m_s(filename, mode)
+#endif // MPT_LIBCXX_MS
+	{
+		if (!stream().is_open()) {
+			stream().setstate(mpt::IO::ofstream::failbit);
+		}
+	}
+
+	mpt::IO::ofstream & stream() {
+		return m_s;
+	}
+
+	operator mpt::IO::ofstream &() {
+		return stream();
+	}
+
+	const mpt::IO::ofstream & stream() const {
+		return m_s;
+	}
+
+	operator const mpt::IO::ofstream &() const {
+		return stream();
+	}
+
+	operator bool() const {
+		return stream() ? true : false;
+	}
+
+	bool operator!() const {
+		return stream().operator!();
+	}
+
+	// cppcheck-suppress exceptThrowInDestructor
+	~SafeOutputFile() noexcept(false) {
+		const bool mayThrow = (std::uncaught_exceptions() == 0);
+		if (!stream()) {
+#if MPT_LIBCXX_MS
+			if (m_f) {
+				std::fclose(m_f);
+			}
+#endif // MPT_LIBCXX_MS
+			if (mayThrow && (stream().exceptions() & (std::ios::badbit | std::ios::failbit))) {
+				// cppcheck-suppress exceptThrowInDestructor
+				throw std::ios_base::failure(std::string("Error before flushing file buffers."));
+			}
+			return;
+		}
+		if (!stream().rdbuf()) {
+#if MPT_LIBCXX_MS
+			if (m_f) {
+				std::fclose(m_f);
+			}
+#endif // MPT_LIBCXX_MS
+			if (mayThrow && (stream().exceptions() & (std::ios::badbit | std::ios::failbit))) {
+				// cppcheck-suppress exceptThrowInDestructor
+				throw std::ios_base::failure(std::string("Error before flushing file buffers."));
+			}
+			return;
+		}
+#if MPT_LIBCXX_MS
+		if (!m_f) {
+			return;
+		}
+#endif // MPT_LIBCXX_MS
+		bool errorOnFlush = false;
+		if (m_FlushMode != FlushMode::None) {
+			try {
+				if (stream().rdbuf()->pubsync() != 0) {
+					errorOnFlush = true;
+				}
+			} catch (const std::exception &) {
+				errorOnFlush = true;
+#if MPT_LIBCXX_MS
+				if (m_FlushMode != FlushMode::None) {
+					if (std::fflush(m_f) != 0) {
+						errorOnFlush = true;
+					}
+				}
+				if (std::fclose(m_f) != 0) {
+					errorOnFlush = true;
+				}
+#endif // MPT_LIBCXX_MS
+				if (mayThrow) {
+					// ignore errorOnFlush here, and re-throw the earlier exception
+					// cppcheck-suppress exceptThrowInDestructor
+					throw;
+				}
+			}
+		}
+#if MPT_LIBCXX_MS
+		if (m_FlushMode != FlushMode::None) {
+			if (std::fflush(m_f) != 0) {
+				errorOnFlush = true;
+			}
+		}
+		if (std::fclose(m_f) != 0) {
+			errorOnFlush = true;
+		}
+#endif // MPT_LIBCXX_MS
+		if (mayThrow && errorOnFlush && (stream().exceptions() & (std::ios::badbit | std::ios::failbit))) {
+			// cppcheck-suppress exceptThrowInDestructor
+			throw std::ios_base::failure(std::string("Error flushing file buffers."));
+		}
+	}
+};
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_FILE_OUTPUTFILE_HPP

Property changes on: src/mpt/io_file/outputfile.hpp
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/x-c++hdr
\ No newline at end of property
Index: src/mpt/io_file/unique_basename.hpp
===================================================================
--- src/mpt/io_file/unique_basename.hpp	(nonexistent)
+++ src/mpt/io_file/unique_basename.hpp	(working copy)
@@ -0,0 +1,95 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_FILE_UNIQUE_BASENAME_HPP
+#define MPT_IO_FILE_UNIQUE_BASENAME_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/path/os_path.hpp"
+#include "mpt/random/default_engines.hpp"
+#include "mpt/random/device.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+#include "mpt/uuid/uuid.hpp"
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+class unique_basename {
+
+private:
+	mpt::os_path m_Basename;
+
+private:
+	static mpt::good_engine make_prng() {
+		mpt::sane_random_device rd;
+		return mpt::make_prng<mpt::good_engine>(rd);
+	}
+
+	static mpt::UUID make_uuid() {
+		mpt::good_engine rng = make_prng();
+		return mpt::UUID::Generate(rng);
+	}
+
+public:
+	explicit unique_basename(mpt::UUID uuid)
+		: m_Basename(mpt::transcode<mpt::os_path>(uuid.ToUString()))
+	{
+		return;
+	}
+
+	explicit unique_basename(const mpt::os_path & prefix, mpt::UUID uuid)
+		: m_Basename(prefix + mpt::transcode<mpt::os_path>(uuid.ToUString()))
+	{
+		return;
+	}
+
+	template <typename Trng>
+	explicit unique_basename(Trng & rng)
+		: m_Basename(mpt::transcode<mpt::os_path>(mpt::UUID::Generate(rng).ToUString()))
+	{
+		return;
+	}
+
+	template <typename Trng>
+	explicit unique_basename(const mpt::os_path & prefix, Trng & rng)
+		: m_Basename(prefix + mpt::transcode<mpt::os_path>(mpt::UUID::Generate(rng).ToUString()))
+	{
+		return;
+	}
+
+	explicit unique_basename()
+		: m_Basename(mpt::transcode<mpt::os_path>(make_uuid().ToUString()))
+	{
+		return;
+	}
+
+public:
+	operator mpt::os_path() const {
+		return m_Basename;
+	}
+
+}; // class unique_basename
+
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_FILE_UNIQUE_BASENAME_HPP

Property changes on: src/mpt/io_file/unique_basename.hpp
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/x-c++hdr
\ No newline at end of property
Index: src/mpt/io_file/unique_tempfilename.hpp
===================================================================
--- src/mpt/io_file/unique_tempfilename.hpp	(nonexistent)
+++ src/mpt/io_file/unique_tempfilename.hpp	(working copy)
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_FILE_UNIQUE_TEMPFILENAME_HPP
+#define MPT_IO_FILE_UNIQUE_TEMPFILENAME_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/io_file/unique_basename.hpp"
+#include "mpt/fs/fs.hpp"
+#include "mpt/path/native_path.hpp"
+#include "mpt/path/os_path.hpp"
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+class unique_tempfilename {
+
+private:
+	mpt::os_path m_Filename;
+
+public:
+	unique_tempfilename(const unique_basename & basename, const mpt::os_path & suffix = MPT_OS_PATH(".tmp"))
+		: m_Filename(mpt::fs<mpt::native_path>{}.get_temp_directory().WithTrailingSlash() + static_cast<mpt::os_path>(basename) + suffix)
+	{
+		return;
+	}
+
+public:
+	operator mpt::os_path() const {
+		return m_Filename;
+	}
+
+}; // class unique_tempfilename
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_FILE_UNIQUE_TEMPFILENAME_HPP

Property changes on: src/mpt/io_file/unique_tempfilename.hpp
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/x-c++hdr
\ No newline at end of property
Index: test/test.cpp
===================================================================
--- test/test.cpp	(revision 17577)
+++ test/test.cpp	(working copy)
@@ -60,6 +60,10 @@
 #include "../mptrack/Settings.h"
 #include "../mptrack/HTTP.h"
 #endif // MODPLUG_TRACKER
+#include "mpt/io_file/fileref.hpp"
+#include "mpt/io_file/inputfile.hpp"
+#include "mpt/io_file/inputfile_filecursor.hpp"
+#include "mpt/io_file/outputfile.hpp"
 #include "../common/mptFileIO.h"
 #include "../common/mptFS.h"
 #ifdef MODPLUG_TRACKER
@@ -993,7 +997,7 @@
 		data.push_back(mpt::as_byte(2));
 		mpt::PathString fn = GetTempFilenameBase() + P_("lazy");
 		RemoveFile(fn);
-		mpt::LazyFileRef f(fn);
+		mpt::IO::FileRef f(fn);
 		f = data;
 		std::vector<std::byte> data2;
 		data2 = f;
Index: unarchiver/unrar.cpp
===================================================================
--- unarchiver/unrar.cpp	(revision 17577)
+++ unarchiver/unrar.cpp	(working copy)
@@ -12,6 +12,9 @@
 
 #ifdef MPT_WITH_UNRAR
 
+#include "mpt/string_transcode/transcode.hpp"
+#include "mpt/uuid/uuid.hpp"
+#include "../common/mptRandom.h"
 #include "../common/mptFileIO.h"
 
 #if MPT_OS_WINDOWS
@@ -95,7 +98,7 @@
 	// files are pretty useless for OpenMPT anyway).
 
 	// Early reject files with no Rar! magic
-	// so that we do not have to instantiate OnDiskFileWrapper.
+	// so that we do not have to instantiate FileAdapter.
 	inFile.Rewind();
 	if(!inFile.ReadMagic("Rar!\x1A"))
 	{
@@ -103,13 +106,13 @@
 	}
 	inFile.Rewind();
 
-	diskFile = std::make_unique<OnDiskFileWrapper>(inFile);
+	diskFile = std::make_unique<mpt::IO::FileAdapter<FileCursor>>(inFile, mpt::IO::unique_tempfilename{mpt::IO::unique_basename{P_("OpenMPT"), mpt::UUID::GenerateLocalUseOnly(mpt::global_prng())}, P_(".rar")});
 	if(!diskFile->IsValid())
 	{
 		return;
 	}
 
-	std::wstring ArcName = diskFile->GetFilename().ToWide();
+	std::wstring ArcName = mpt::transcode<std::wstring>(diskFile->GetFilename());
 	std::vector<wchar_t> ArcNameBuf(ArcName.c_str(), ArcName.c_str() + ArcName.length() + 1);
 	std::vector<wchar_t> CmtBuf(65536);
 	RAROpenArchiveDataEx ArchiveData = {};
@@ -199,7 +202,7 @@
 		return false;
 	}
 
-	std::wstring ArcName = diskFile->GetFilename().ToWide();
+	std::wstring ArcName = mpt::transcode<std::wstring>(diskFile->GetFilename());
 	std::vector<wchar_t> ArcNameBuf(ArcName.c_str(), ArcName.c_str() + ArcName.length() + 1);
 	RAROpenArchiveDataEx ArchiveData = {};
 	ArchiveData.OpenMode = RAR_OM_EXTRACT;
Index: unarchiver/unrar.h
===================================================================
--- unarchiver/unrar.h	(revision 17577)
+++ unarchiver/unrar.h	(working copy)
@@ -11,6 +11,10 @@
 
 #include "openmpt/all/BuildSettings.hpp"
 
+#include "mpt/io_file/fileadapter.hpp"
+
+#include "../common/FileReader.h"
+
 #include "archive.h"
 
 OPENMPT_NAMESPACE_BEGIN
@@ -23,7 +27,7 @@
 
 protected:
 
-	std::unique_ptr<OnDiskFileWrapper> diskFile;
+	std::unique_ptr<mpt::IO::FileAdapter<FileCursor>> diskFile;
 	bool captureCurrentFile = false;
 
 public:
