#include "il2cpp-config.h" #if IL2CPP_TARGET_WINDOWS #include "WindowsHelpers.h" #undef CopyFile #undef DeleteFile #undef MoveFile #undef ReplaceFile #undef GetFileAttributes #undef SetFileAttributes #undef CreatePipe #include "os/File.h" #include "utils/Expected.h" #include "utils/Il2CppError.h" #include "utils/StringUtils.h" #include "utils/PathUtils.h" #if IL2CPP_TARGET_WINRT #include "os/BrokeredFileSystem.h" #endif #include static inline int FileWin32ErrorToErrorCode(DWORD win32ErrorCode) { return win32ErrorCode; } namespace il2cpp { namespace os { #if IL2CPP_TARGET_WINDOWS_DESKTOP utils::Expected File::Isatty(FileHandle* fileHandle) { DWORD mode; return GetConsoleMode((HANDLE)fileHandle, &mode) != 0; } #elif IL2CPP_TARGET_WINDOWS_GAMES utils::Expected File::Isatty(FileHandle* fileHandle) { return utils::Il2CppError(utils::NotSupported, "Console functions are not supported on Windows Games platforms."); } #endif #if IL2CPP_TARGET_WINDOWS_DESKTOP || IL2CPP_TARGET_WINDOWS_GAMES FileHandle* File::GetStdInput() { return (FileHandle*)GetStdHandle(STD_INPUT_HANDLE); } FileHandle* File::GetStdError() { return (FileHandle*)GetStdHandle(STD_ERROR_HANDLE); } FileHandle* File::GetStdOutput() { return (FileHandle*)GetStdHandle(STD_OUTPUT_HANDLE); } #endif // IL2CPP_TARGET_WINDOWS_DESKTOP || IL2CPP_TARGET_WINDOWS_GAMES utils::Expected File::CreatePipe(FileHandle** read_handle, FileHandle** write_handle) { int error; return CreatePipe(read_handle, write_handle, &error); } utils::Expected File::CreatePipe(FileHandle** read_handle, FileHandle** write_handle, int* error) { #if IL2CPP_TARGET_WINDOWS_DESKTOP || IL2CPP_TARGET_WINDOWS_GAMES SECURITY_ATTRIBUTES attr; attr.nLength = sizeof(SECURITY_ATTRIBUTES); attr.bInheritHandle = TRUE; attr.lpSecurityDescriptor = NULL; bool ret = ::CreatePipe((PHANDLE)read_handle, (PHANDLE)write_handle, &attr, 0); if (ret == FALSE) { *error = GetLastError(); /* FIXME: throw an exception? */ return false; } return true; #else // IL2CPP_TARGET_WINDOWS_DESKTOP || IL2CPP_TARGET_WINDOWS_GAMES return utils::Il2CppError(utils::NotSupported, "Pipes are not supported on WinRT based platforms."); #endif // IL2CPP_TARGET_WINDOWS_DESKTOP || IL2CPP_TARGET_WINDOWS_GAMES } #if !IL2CPP_TARGET_XBOXONE && !IL2CPP_TARGET_WINDOWS_GAMES UnityPalFileAttributes File::GetFileAttributes(const std::string& path, int *error) { const UTF16String utf16Path(utils::StringUtils::Utf8ToUtf16(path.c_str())); WIN32_FILE_ATTRIBUTE_DATA fileAttributes; BOOL result = ::GetFileAttributesExW((LPCWSTR)utf16Path.c_str(), GetFileExInfoStandard, &fileAttributes); if (result == FALSE) { auto lastError = ::GetLastError(); #if IL2CPP_TARGET_WINRT if (lastError == ERROR_ACCESS_DENIED) return BrokeredFileSystem::GetFileAttributesW(utf16Path, error); #endif *error = FileWin32ErrorToErrorCode(lastError); return static_cast(INVALID_FILE_ATTRIBUTES); } *error = kErrorCodeSuccess; return static_cast(fileAttributes.dwFileAttributes); } #endif // !IL2CPP_TARGET_XBOXONE && !IL2CPP_TARGET_WINDOWS_GAMES bool File::SetFileAttributes(const std::string& path, UnityPalFileAttributes attributes, int* error) { const UTF16String utf16Path(utils::StringUtils::Utf8ToUtf16(path.c_str())); *error = kErrorCodeSuccess; if (::SetFileAttributesW((LPCWSTR)utf16Path.c_str(), attributes)) return true; auto lastError = ::GetLastError(); #if IL2CPP_TARGET_WINRT if (lastError == ERROR_ACCESS_DENIED) return BrokeredFileSystem::SetFileAttributesW(utf16Path, attributes, error); #endif *error = FileWin32ErrorToErrorCode(lastError); return false; } static inline int64_t HighAndLowToInt64(uint32_t high, uint32_t low) { return ((uint64_t)high << 32) + low; } static inline int64_t FileTimeToInt64(const FILETIME& fileTime) { return HighAndLowToInt64(fileTime.dwHighDateTime, fileTime.dwLowDateTime); } bool File::GetFileStat(const std::string& path, il2cpp::os::FileStat * stat, int* error) { *error = kErrorCodeSuccess; const UTF16String utf16Path(utils::StringUtils::Utf8ToUtf16(path.c_str())); WIN32_FILE_ATTRIBUTE_DATA data; if (!::GetFileAttributesExW((LPCWSTR)utf16Path.c_str(), GetFileExInfoStandard, &data)) { auto lastError = ::GetLastError(); #if IL2CPP_TARGET_WINRT if (lastError == ERROR_ACCESS_DENIED) return BrokeredFileSystem::GetFileStat(path, utf16Path, stat, error); #endif *error = FileWin32ErrorToErrorCode(lastError); return false; } stat->name = il2cpp::utils::PathUtils::Basename(path); stat->attributes = data.dwFileAttributes; stat->creation_time = FileTimeToInt64(data.ftCreationTime); stat->last_access_time = FileTimeToInt64(data.ftLastAccessTime); stat->last_write_time = FileTimeToInt64(data.ftLastWriteTime); stat->length = HighAndLowToInt64(data.nFileSizeHigh, data.nFileSizeLow); return true; } FileType File::GetFileType(FileHandle* handle) { int result = ::GetFileType((HANDLE)handle); /*if (result == FILE_TYPE_UNKNOWN) { *error = GetLastError(); }*/ return (FileType)result; } bool File::CopyFile(const std::string& src, const std::string& dest, bool overwrite, int* error) { const UTF16String utf16Src(utils::StringUtils::Utf8ToUtf16(src.c_str())); const UTF16String utf16Dest(utils::StringUtils::Utf8ToUtf16(dest.c_str())); *error = kErrorCodeSuccess; if (::CopyFileW((LPWSTR)utf16Src.c_str(), (LPWSTR)utf16Dest.c_str(), overwrite ? FALSE : TRUE)) return true; auto lastError = ::GetLastError(); #if IL2CPP_TARGET_WINRT if (lastError == ERROR_ACCESS_DENIED) return BrokeredFileSystem::CopyFileW(utf16Src, utf16Dest, overwrite, error); #endif *error = FileWin32ErrorToErrorCode(lastError); return false; } bool File::MoveFile(const std::string& src, const std::string& dest, int* error) { const UTF16String utf16Src(utils::StringUtils::Utf8ToUtf16(src.c_str())); const UTF16String utf16Dest(utils::StringUtils::Utf8ToUtf16(dest.c_str())); *error = kErrorCodeSuccess; if (::MoveFileExW((LPWSTR)utf16Src.c_str(), (LPWSTR)utf16Dest.c_str(), MOVEFILE_COPY_ALLOWED)) return true; auto lastError = ::GetLastError(); #if IL2CPP_TARGET_WINRT if (lastError == ERROR_ACCESS_DENIED) return BrokeredFileSystem::MoveFileW(utf16Src, utf16Dest, error); #endif *error = FileWin32ErrorToErrorCode(lastError); return false; } bool File::DeleteFile(const std::string& path, int *error) { *error = kErrorCodeSuccess; const UTF16String utf16Path(utils::StringUtils::Utf8ToUtf16(path.c_str())); if (::DeleteFileW((LPWSTR)utf16Path.c_str())) return true; auto lastError = ::GetLastError(); #if IL2CPP_TARGET_WINRT if (lastError == ERROR_ACCESS_DENIED) { *error = BrokeredFileSystem::DeleteFileW(utf16Path); return *error == kErrorCodeSuccess; } #endif *error = FileWin32ErrorToErrorCode(lastError); return false; } bool File::ReplaceFile(const std::string& sourceFileName, const std::string& destinationFileName, const std::string& destinationBackupFileName, bool ignoreMetadataErrors, int* error) { const UTF16String utf16Src(utils::StringUtils::Utf8ToUtf16(sourceFileName.c_str())); const UTF16String utf16Dest(utils::StringUtils::Utf8ToUtf16(destinationFileName.c_str())); const UTF16String utf16Backup(utils::StringUtils::Utf8ToUtf16(destinationBackupFileName.c_str())); *error = kErrorCodeSuccess; DWORD flags = REPLACEFILE_WRITE_THROUGH; if (ignoreMetadataErrors) flags |= REPLACEFILE_IGNORE_MERGE_ERRORS; if (::ReplaceFileW((LPWSTR)utf16Dest.c_str(), (LPWSTR)utf16Src.c_str(), utf16Backup.empty() ? NULL : (LPWSTR)utf16Backup.c_str(), flags, NULL, NULL)) return true; *error = FileWin32ErrorToErrorCode(::GetLastError()); return false; } static inline int MonoToWindowsOpenMode(int monoOpenMode) { switch (monoOpenMode) { case kFileModeCreateNew: return CREATE_NEW; case kFileModeCreate: return CREATE_ALWAYS; case kFileModeOpen: return OPEN_EXISTING; case kFileModeOpenOrCreate: case kFileModeAppend: return OPEN_ALWAYS; case kFileModeTruncate: return TRUNCATE_EXISTING; default: Assert(false && "Unknown mono open mode"); IL2CPP_UNREACHABLE; } } static inline int MonoToWindowsAccessMode(int monoAccessMode) { switch (monoAccessMode) { case kFileAccessRead: return GENERIC_READ; case kFileAccessWrite: return GENERIC_WRITE; case kFileAccessExecute: return GENERIC_EXECUTE; case kFileAccessReadWrite: return GENERIC_READ | GENERIC_WRITE; case kFileAccessReadWriteExecute: return GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE; default: return 0; } } static inline DWORD MonoOptionsToWindowsFlagsAndAttributes(const std::string& path, int options) { DWORD flagsAndAttributes; if (options != 0) { if (options & kFileOptionsEncrypted) flagsAndAttributes = FILE_ATTRIBUTE_ENCRYPTED; else flagsAndAttributes = FILE_ATTRIBUTE_NORMAL; if (options & kFileOptionsDeleteOnClose) flagsAndAttributes |= FILE_FLAG_DELETE_ON_CLOSE; if (options & kFileOptionsSequentialScan) flagsAndAttributes |= FILE_FLAG_SEQUENTIAL_SCAN; if (options & kFileOptionsRandomAccess) flagsAndAttributes |= FILE_FLAG_RANDOM_ACCESS; if (options & kFileOptionsWriteThrough) flagsAndAttributes |= FILE_FLAG_WRITE_THROUGH; } else { flagsAndAttributes = FILE_ATTRIBUTE_NORMAL; } int error; UnityPalFileAttributes currentAttributes = File::GetFileAttributes(path, &error); if (currentAttributes != INVALID_FILE_ATTRIBUTES && (currentAttributes & FILE_ATTRIBUTE_DIRECTORY)) flagsAndAttributes |= FILE_FLAG_BACKUP_SEMANTICS; // Required to open a directory return flagsAndAttributes; } FileHandle* File::Open(const std::string& path, int openMode, int accessMode, int shareMode, int options, int *error) { const UTF16String utf16Path(utils::StringUtils::Utf8ToUtf16(path.c_str())); openMode = MonoToWindowsOpenMode(openMode); accessMode = MonoToWindowsAccessMode(accessMode); DWORD flagsAndAttributes = MonoOptionsToWindowsFlagsAndAttributes(path, options); HANDLE handle = ::CreateFileW((LPCWSTR)utf16Path.c_str(), accessMode, shareMode, NULL, openMode, flagsAndAttributes, NULL); if (INVALID_HANDLE_VALUE == handle) { auto lastError = ::GetLastError(); #if IL2CPP_TARGET_WINRT if (lastError == ERROR_ACCESS_DENIED) return BrokeredFileSystem::Open(utf16Path, accessMode, shareMode, openMode, flagsAndAttributes, error); #endif *error = FileWin32ErrorToErrorCode(lastError); return (FileHandle*)INVALID_HANDLE_VALUE; } *error = kErrorCodeSuccess; return (FileHandle*)handle; } bool File::Close(FileHandle* handle, int *error) { *error = kErrorCodeSuccess; if (CloseHandle((HANDLE)handle)) return true; *error = FileWin32ErrorToErrorCode(::GetLastError()); return false; } bool File::SetFileTime(FileHandle* handle, int64_t creation_time, int64_t last_access_time, int64_t last_write_time, int* error) { FILE_BASIC_INFO fileInfo; fileInfo.CreationTime.QuadPart = creation_time; fileInfo.LastAccessTime.QuadPart = last_access_time; fileInfo.LastWriteTime.QuadPart = last_write_time; fileInfo.ChangeTime.QuadPart = 0; // 0 means don't change anything fileInfo.FileAttributes = 0; // 0 means don't change anything if (SetFileInformationByHandle(handle, FileBasicInfo, &fileInfo, sizeof(FILE_BASIC_INFO)) == FALSE) { *error = GetLastError(); return false; } *error = kErrorCodeSuccess; return true; } int64_t File::GetLength(FileHandle* handle, int *error) { *error = kErrorCodeSuccess; LARGE_INTEGER size; if (!::GetFileSizeEx((HANDLE)handle, &size)) { *error = FileWin32ErrorToErrorCode(::GetLastError()); return 0; } return size.QuadPart; } #if !IL2CPP_USE_GENERIC_FILE bool File::Truncate(FileHandle* handle, int *error) { *error = kErrorCodeSuccess; if (!::SetEndOfFile((HANDLE)handle)) { *error = FileWin32ErrorToErrorCode(::GetLastError()); return false; } return true; } #endif // IL2CPP_USE_GENERIC_FILE bool File::SetLength(FileHandle* handle, int64_t length, int *error) { *error = kErrorCodeSuccess; LARGE_INTEGER zeroOffset = { 0 }; LARGE_INTEGER requestedOffset = { 0 }; requestedOffset.QuadPart = length; LARGE_INTEGER initialPosition = { 0 }; // set position to 0 from current to retrieve current position if (!::SetFilePointerEx((HANDLE)handle, zeroOffset, &initialPosition, FILE_CURRENT)) { *error = FileWin32ErrorToErrorCode(::GetLastError()); return false; } // seek to requested length if (!::SetFilePointerEx((HANDLE)handle, requestedOffset, NULL, FILE_BEGIN)) { *error = FileWin32ErrorToErrorCode(::GetLastError()); return false; } // set requested length if (!::SetEndOfFile((HANDLE)handle)) { *error = FileWin32ErrorToErrorCode(::GetLastError()); return false; } // restore original position if (!::SetFilePointerEx((HANDLE)handle, initialPosition, NULL, FILE_BEGIN)) { *error = FileWin32ErrorToErrorCode(::GetLastError()); return false; } return true; } int64_t File::Seek(FileHandle* handle, int64_t offset, int origin, int *error) { *error = kErrorCodeSuccess; LARGE_INTEGER distance; distance.QuadPart = offset; LARGE_INTEGER position = { 0 }; if (!::SetFilePointerEx((HANDLE)handle, distance, &position, origin)) *error = FileWin32ErrorToErrorCode(::GetLastError()); return position.QuadPart; } int File::Read(FileHandle* handle, char *dest, int count, int *error) { *error = kErrorCodeSuccess; DWORD bytesRead = 0; if (!::ReadFile(handle, dest, count, &bytesRead, NULL)) *error = FileWin32ErrorToErrorCode(::GetLastError()); return bytesRead; } int32_t File::Write(FileHandle* handle, const char* buffer, int count, int *error) { int32_t written; BOOL success = WriteFile((HANDLE)handle, buffer, count, (LPDWORD)&written, NULL); if (!success) { DWORD originalError = GetLastError(); if (originalError == ERROR_INVALID_PARAMETER) { // Maybe this is an async file write, so try with those parameters. OVERLAPPED overlapped = {0}; success = WriteFile((HANDLE)handle, buffer, count, NULL, &overlapped); if (success != 0 || GetLastError() == ERROR_IO_PENDING) { success = TRUE; // The async write succeeded. Now get the number of bytes written. #if IL2CPP_TARGET_WINDOWS_DESKTOP if (GetOverlappedResult((HANDLE)handle, &overlapped, (LPDWORD)&written, TRUE) == 0) #else if (GetOverlappedResultEx((HANDLE)handle, &overlapped, (LPDWORD)&written, INFINITE, FALSE) == 0) #endif { // Oops, we could not get the number of bytes writen, so return an error. *error = GetLastError(); return -1; } } } if (!success) { *error = originalError; return -1; } } return written; } bool File::Flush(FileHandle* handle, int* error) { *error = kErrorCodeSuccess; if (FlushFileBuffers((HANDLE)handle)) return true; *error = FileWin32ErrorToErrorCode(::GetLastError()); return false; } void File::Lock(FileHandle* handle, int64_t position, int64_t length, int* error) { *error = kErrorCodeSuccess; OVERLAPPED overlapped; ZeroMemory(&overlapped, sizeof(overlapped)); overlapped.Offset = position & 0xFFFFFFFF; overlapped.OffsetHigh = position >> 32; LARGE_INTEGER lengthUnion; lengthUnion.QuadPart = length; if (!::LockFileEx((HANDLE)handle, LOCKFILE_FAIL_IMMEDIATELY, 0, lengthUnion.LowPart, lengthUnion.HighPart, &overlapped)) *error = FileWin32ErrorToErrorCode(::GetLastError()); } void File::Unlock(FileHandle* handle, int64_t position, int64_t length, int* error) { *error = kErrorCodeSuccess; OVERLAPPED overlapped; ZeroMemory(&overlapped, sizeof(overlapped)); overlapped.Offset = position & 0xFFFFFFFF; overlapped.OffsetHigh = position >> 32; LARGE_INTEGER lengthUnion; lengthUnion.QuadPart = length; if (!::UnlockFileEx((HANDLE)handle, 0, lengthUnion.LowPart, lengthUnion.HighPart, &overlapped)) *error = FileWin32ErrorToErrorCode(::GetLastError()); } utils::Expected File::DuplicateHandle(FileHandle* source_process_handle, FileHandle* source_handle, FileHandle* target_process_handle, FileHandle** target_handle, int access, int inherit, int options, int* error) { /* This is only used on Windows */ //MONO_PREPARE_BLOCKING; BOOL ret = ::DuplicateHandle((HANDLE)source_process_handle, (HANDLE)source_handle, (HANDLE)target_process_handle, (LPHANDLE)target_handle, access, inherit, options); //MONO_FINISH_BLOCKING; if (ret == FALSE) { *error = GetLastError(); /* FIXME: throw an exception? */ return false; } return true; } static bool ends_with(const std::string& value, const std::string& ending) { if (value.length() >= ending.length()) return value.compare(value.length() - ending.length(), ending.length(), ending) == 0; return false; } utils::Expected File::IsExecutable(const std::string& path) { return ends_with(path, "exe"); } bool File::Cancel(FileHandle* handle) { return CancelIoEx((HANDLE)handle, NULL); } } } #endif