Bundle libOpenMPT as a dynamic framework, which should be safe once again, now that there is only one version to bundle. Also, now it is using the versions of libvorbisfile and libmpg123 that are bundled with the player, instead of compiling minimp3 and stbvorbis. Signed-off-by: Christopher Snowhill <kode54@gmail.com>
608 lines
13 KiB
C++
608 lines
13 KiB
C++
/*
|
|
* mptFileIO.cpp
|
|
* -------------
|
|
* Purpose: File I/O wrappers
|
|
* Notes : (currently none)
|
|
* Authors: OpenMPT Devs
|
|
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
|
|
*/
|
|
|
|
|
|
#include "stdafx.h"
|
|
#include "mptFileIO.h"
|
|
|
|
#if defined(MPT_ENABLE_FILEIO)
|
|
#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(MPT_ENABLE_FILEIO)
|
|
#include <stdexcept>
|
|
#endif // MPT_ENABLE_FILEIO
|
|
|
|
#ifdef MODPLUG_TRACKER
|
|
#if MPT_OS_WINDOWS
|
|
#include <windows.h>
|
|
#include <WinIoCtl.h>
|
|
#include <io.h>
|
|
#endif // MPT_OS_WINDOWS
|
|
#endif // MODPLUG_TRACKER
|
|
|
|
#if defined(MPT_ENABLE_FILEIO)
|
|
#if MPT_COMPILER_MSVC
|
|
#include <stdio.h>
|
|
#include <tchar.h>
|
|
#endif // MPT_COMPILER_MSVC
|
|
#endif // MPT_ENABLE_FILEIO
|
|
|
|
|
|
|
|
OPENMPT_NAMESPACE_BEGIN
|
|
|
|
|
|
#if defined(MPT_ENABLE_FILEIO)
|
|
|
|
|
|
|
|
#if !defined(MPT_BUILD_SILENCE_LIBOPENMPT_CONFIGURATION_WARNINGS)
|
|
|
|
#if defined(MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR)
|
|
#if MPT_GCC_BEFORE(9,1,0)
|
|
MPT_WARNING("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.")
|
|
#endif // MPT_GCC_AT_LEAST(9,1,0)
|
|
#endif // MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR
|
|
|
|
#endif // !MPT_BUILD_SILENCE_LIBOPENMPT_CONFIGURATION_WARNINGS
|
|
|
|
|
|
|
|
#ifdef MODPLUG_TRACKER
|
|
#if MPT_OS_WINDOWS
|
|
bool SetFilesystemCompression(HANDLE hFile)
|
|
{
|
|
if(hFile == INVALID_HANDLE_VALUE)
|
|
{
|
|
return false;
|
|
}
|
|
USHORT format = COMPRESSION_FORMAT_DEFAULT;
|
|
DWORD dummy = 0;
|
|
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)
|
|
{
|
|
return false;
|
|
}
|
|
uintptr_t fhandle = _get_osfhandle(fd);
|
|
HANDLE hFile = (HANDLE)fhandle;
|
|
if(hFile == INVALID_HANDLE_VALUE)
|
|
{
|
|
return false;
|
|
}
|
|
return SetFilesystemCompression(hFile);
|
|
}
|
|
bool SetFilesystemCompression(const mpt::PathString &filename)
|
|
{
|
|
DWORD attributes = GetFileAttributes(filename.AsNativePrefixed().c_str());
|
|
if(attributes == INVALID_FILE_ATTRIBUTES)
|
|
{
|
|
return false;
|
|
}
|
|
if(attributes & FILE_ATTRIBUTE_COMPRESSED)
|
|
{
|
|
return true;
|
|
}
|
|
HANDLE hFile = CreateFile(filename.AsNativePrefixed().c_str(), GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
|
|
if(hFile == INVALID_HANDLE_VALUE)
|
|
{
|
|
return false;
|
|
}
|
|
bool result = SetFilesystemCompression(hFile);
|
|
CloseHandle(hFile);
|
|
return result;
|
|
}
|
|
#endif // MPT_OS_WINDOWS
|
|
#endif // MODPLUG_TRACKER
|
|
|
|
|
|
|
|
#ifdef MODPLUG_TRACKER
|
|
|
|
namespace mpt {
|
|
|
|
#if MPT_COMPILER_MSVC
|
|
|
|
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(filename.AsNativePrefixed().c_str(), fopen_mode.c_str())
|
|
#else
|
|
std::fopen(filename.AsNativePrefixed().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_COMPILER_MSVC
|
|
|
|
// cppcheck-suppress exceptThrowInDestructor
|
|
SafeOutputFile::~SafeOutputFile() noexcept(false)
|
|
{
|
|
const bool mayThrow = (std::uncaught_exceptions() == 0);
|
|
if(!stream())
|
|
{
|
|
#if MPT_COMPILER_MSVC
|
|
if(m_f)
|
|
{
|
|
std::fclose(m_f);
|
|
}
|
|
#endif // MPT_COMPILER_MSVC
|
|
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_COMPILER_MSVC
|
|
if(m_f)
|
|
{
|
|
std::fclose(m_f);
|
|
}
|
|
#endif // MPT_COMPILER_MSVC
|
|
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_COMPILER_MSVC
|
|
if(!m_f)
|
|
{
|
|
return;
|
|
}
|
|
#endif // MPT_COMPILER_MSVC
|
|
bool errorOnFlush = false;
|
|
if(m_FlushMode != FlushMode::None)
|
|
{
|
|
try
|
|
{
|
|
if(stream().rdbuf()->pubsync() != 0)
|
|
{
|
|
errorOnFlush = true;
|
|
}
|
|
} catch(const std::exception &)
|
|
{
|
|
errorOnFlush = true;
|
|
#if MPT_COMPILER_MSVC
|
|
if(m_FlushMode != FlushMode::None)
|
|
{
|
|
if(std::fflush(m_f) != 0)
|
|
{
|
|
errorOnFlush = true;
|
|
}
|
|
}
|
|
if(std::fclose(m_f) != 0)
|
|
{
|
|
errorOnFlush = true;
|
|
}
|
|
#endif // MPT_COMPILER_MSVC
|
|
if(mayThrow)
|
|
{
|
|
// ignore errorOnFlush here, and re-throw the earlier exception
|
|
// cppcheck-suppress exceptThrowInDestructor
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
#if MPT_COMPILER_MSVC
|
|
if(m_FlushMode != FlushMode::None)
|
|
{
|
|
if(std::fflush(m_f) != 0)
|
|
{
|
|
errorOnFlush = true;
|
|
}
|
|
}
|
|
if(std::fclose(m_f) != 0)
|
|
{
|
|
errorOnFlush = true;
|
|
}
|
|
#endif // MPT_COMPILER_MSVC
|
|
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_IsValid(false)
|
|
, m_IsCached(false)
|
|
{
|
|
MPT_ASSERT(!filename.empty());
|
|
Open(filename, allowWholeFileCaching);
|
|
}
|
|
|
|
InputFile::~InputFile()
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
bool InputFile::Open(const mpt::PathString &filename, bool allowWholeFileCaching)
|
|
{
|
|
m_IsCached = false;
|
|
m_Cache.resize(0);
|
|
m_Cache.shrink_to_fit();
|
|
m_Filename = filename;
|
|
m_File.open(m_Filename, std::ios::binary | std::ios::in);
|
|
if(allowWholeFileCaching)
|
|
{
|
|
if(mpt::IO::IsReadSeekable(m_File))
|
|
{
|
|
if(!mpt::IO::SeekEnd(m_File))
|
|
{
|
|
m_File.close();
|
|
return false;
|
|
}
|
|
mpt::IO::Offset filesize = mpt::IO::TellRead(m_File);
|
|
if(!mpt::IO::SeekBegin(m_File))
|
|
{
|
|
m_File.close();
|
|
return false;
|
|
}
|
|
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 false;
|
|
}
|
|
if(!mpt::IO::SeekBegin(m_File))
|
|
{
|
|
m_File.close();
|
|
return false;
|
|
}
|
|
m_IsCached = true;
|
|
m_IsValid = true;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
m_IsValid = true;
|
|
return m_File.good();
|
|
}
|
|
|
|
|
|
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::CreateTempFileName(P_("OpenMPT"), fileNameExtension);
|
|
|
|
#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
|
|
|
|
|
|
OPENMPT_NAMESPACE_END
|