1 // Copyright © 2008-2021 Pioneer Developers. See AUTHORS.txt for details 2 // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt 3 4 #include "Win32Setup.h" 5 6 #include "FileSystem.h" 7 #include "TextUtils.h" 8 #include "libs.h" 9 #include "utils.h" 10 #include <algorithm> 11 #include <cassert> 12 #include <cerrno> 13 14 #define WIN32_LEAN_AND_MEAN 15 #ifndef NOMINMAX 16 #define NOMINMAX 17 #endif 18 #include <windows.h> 19 // GetPiUserDir() needs these 20 #include <shlobj.h> 21 #include <shlwapi.h> 22 23 namespace FileSystem { 24 absolute_path(const std::string & path)25 static std::string absolute_path(const std::string &path) 26 { 27 std::wstring wpath = transcode_utf8_to_utf16(path); 28 wchar_t buf[MAX_PATH + 1]; 29 DWORD len = GetFullPathNameW(wpath.c_str(), MAX_PATH, buf, 0); 30 buf[len] = L'\0'; 31 return transcode_utf16_to_utf8(buf, len); 32 } 33 FindUserDir()34 static std::string FindUserDir() 35 { 36 wchar_t appdata_path[MAX_PATH]; 37 if (SHGetFolderPathW(0, CSIDL_PERSONAL, 0, SHGFP_TYPE_CURRENT, appdata_path) != S_OK) { 38 Output("Couldn't get user documents folder path\n"); 39 exit(-1); 40 } 41 42 std::wstring path(appdata_path); 43 path += L"/Pioneer"; 44 45 if (!PathFileExistsW(path.c_str())) { 46 if (SHCreateDirectoryExW(0, path.c_str(), 0) != ERROR_SUCCESS) { 47 std::string utf8path = transcode_utf16_to_utf8(path); 48 Output("Couldn't create user game folder '%s'", utf8path.c_str()); 49 exit(-1); 50 } 51 } 52 53 return transcode_utf16_to_utf8(path); 54 } 55 FindDataDir()56 static std::string FindDataDir() 57 { 58 static const DWORD BUFSIZE = MAX_PATH + 1; 59 wchar_t buf[BUFSIZE]; 60 memset(buf, L'\0', sizeof(wchar_t) * BUFSIZE); // clear the buffer 61 DWORD dwRet = GetCurrentDirectoryW(BUFSIZE, buf); 62 if (dwRet == 0) { 63 printf("GetCurrentDirectory failed (%d)\n", GetLastError()); 64 abort(); 65 } 66 if (dwRet > BUFSIZE) { 67 printf("Buffer too small; need %d characters\n", dwRet); 68 // I gave you MAX_PATH, what more do you want!? 69 abort(); 70 } 71 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { 72 // I gave you MAX_PATH, what more do you want!? 73 abort(); 74 } 75 PathAppendW(buf, L"data"); 76 return transcode_utf16_to_utf8(buf, wcslen(buf)); 77 } 78 GetUserDir()79 std::string GetUserDir() 80 { 81 static const std::string user_path = FindUserDir(); 82 return user_path; 83 } 84 GetDataDir()85 std::string GetDataDir() 86 { 87 static const std::string data_path = FindDataDir(); 88 return data_path; 89 } 90 FileSourceFS(const std::string & root,bool trusted)91 FileSourceFS::FileSourceFS(const std::string &root, bool trusted) : 92 FileSource((root == "/") ? "" : absolute_path(root), trusted) {} 93 ~FileSourceFS()94 FileSourceFS::~FileSourceFS() {} 95 file_type_for_attributes(DWORD attrs)96 static FileInfo::FileType file_type_for_attributes(DWORD attrs) 97 { 98 FileInfo::FileType ty; 99 if (attrs == INVALID_FILE_ATTRIBUTES) 100 ty = FileInfo::FT_NON_EXISTENT; 101 else if (attrs & FILE_ATTRIBUTE_DIRECTORY) 102 ty = FileInfo::FT_DIR; 103 else if (attrs & FILE_ATTRIBUTE_REPARSE_POINT) { 104 /* symlink, junction point, etc; we should perhaps traverse it */ 105 ty = FileInfo::FT_SPECIAL; 106 } else if (attrs & (FILE_ATTRIBUTE_DEVICE | FILE_ATTRIBUTE_OFFLINE | FILE_ATTRIBUTE_SYSTEM)) { 107 ty = FileInfo::FT_SPECIAL; 108 } else 109 ty = FileInfo::FT_FILE; 110 return ty; 111 } 112 datetime_for_filetime(FILETIME filetime)113 static Time::DateTime datetime_for_filetime(FILETIME filetime) 114 { 115 Time::DateTime dt; 116 117 SYSTEMTIME systime, localtime; 118 FileTimeToSystemTime(&filetime, &systime); 119 SystemTimeToTzSpecificLocalTime(0, &systime, &localtime); 120 121 dt = Time::DateTime( 122 localtime.wYear, localtime.wMonth, localtime.wDay, 123 localtime.wHour, localtime.wMinute, localtime.wSecond); 124 dt += Time::TimeDelta(localtime.wMilliseconds, Time::Millisecond); 125 return dt; 126 } 127 file_modtime_for_handle(HANDLE hfile)128 static Time::DateTime file_modtime_for_handle(HANDLE hfile) 129 { 130 assert(hfile != INVALID_HANDLE_VALUE); 131 132 Time::DateTime modtime; 133 FILETIME filetime; 134 if (GetFileTime(hfile, 0, 0, &filetime)) { 135 modtime = datetime_for_filetime(filetime); 136 } 137 return modtime; 138 } 139 Lookup(const std::string & path)140 FileInfo FileSourceFS::Lookup(const std::string &path) 141 { 142 const std::string fullpath = JoinPathBelow(GetRoot(), path); 143 const std::wstring wfullpath = transcode_utf8_to_utf16(fullpath); 144 DWORD attrs = GetFileAttributesW(wfullpath.c_str()); 145 const FileInfo::FileType ty = file_type_for_attributes(attrs); 146 Time::DateTime modtime; 147 if (ty == FileInfo::FT_FILE || ty == FileInfo::FT_DIR) { 148 HANDLE hfile = CreateFileW(wfullpath.c_str(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); 149 if (hfile != INVALID_HANDLE_VALUE) { 150 modtime = file_modtime_for_handle(hfile); 151 CloseHandle(hfile); 152 } 153 } 154 return MakeFileInfo(path, ty, modtime); 155 } 156 ReadFile(const std::string & path)157 RefCountedPtr<FileData> FileSourceFS::ReadFile(const std::string &path) 158 { 159 const std::string fullpath = JoinPathBelow(GetRoot(), path); 160 const std::wstring wfullpath = transcode_utf8_to_utf16(fullpath); 161 HANDLE filehandle = CreateFileW(wfullpath.c_str(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); 162 if (filehandle == INVALID_HANDLE_VALUE) 163 return RefCountedPtr<FileData>(0); 164 else { 165 const Time::DateTime modtime = file_modtime_for_handle(filehandle); 166 167 LARGE_INTEGER large_size; 168 if (!GetFileSizeEx(filehandle, &large_size)) { 169 Output("failed to get file size for '%s'\n", fullpath.c_str()); 170 CloseHandle(filehandle); 171 abort(); 172 } 173 size_t size = size_t(large_size.QuadPart); 174 175 char *data = static_cast<char *>(std::malloc(size)); 176 if (!data) { 177 // XXX handling memory allocation failure gracefully is too hard right now 178 Output("failed when allocating buffer for '%s'\n", fullpath.c_str()); 179 CloseHandle(filehandle); 180 abort(); 181 } 182 183 if (size > 0x7FFFFFFFull) { 184 Output("file '%s' is too large (can't currently cope with files > 2GB)\n", fullpath.c_str()); 185 CloseHandle(filehandle); 186 abort(); 187 } 188 189 DWORD read_size; 190 BOOL ret = ::ReadFile(filehandle, static_cast<LPVOID>(data), (DWORD)size, &read_size, 0); 191 if (!ret) { 192 Output("error while reading file '%s'\n", fullpath.c_str()); 193 CloseHandle(filehandle); 194 abort(); 195 } 196 if (size_t(read_size) != size) { 197 Output("file '%s' truncated\n", fullpath.c_str()); 198 memset(data + read_size, 0xee, size - read_size); 199 } 200 201 CloseHandle(filehandle); 202 203 return RefCountedPtr<FileData>(new FileDataMalloc(MakeFileInfo(path, FileInfo::FT_FILE, modtime), size, data)); 204 } 205 } 206 ReadDirectory(const std::string & dirpath,std::vector<FileInfo> & output)207 bool FileSourceFS::ReadDirectory(const std::string &dirpath, std::vector<FileInfo> &output) 208 { 209 size_t output_head_size = output.size(); 210 const std::wstring wsearchglob = transcode_utf8_to_utf16(JoinPathBelow(GetRoot(), dirpath)) + L"/*"; 211 WIN32_FIND_DATAW findinfo; 212 HANDLE dirhandle = FindFirstFileW(wsearchglob.c_str(), &findinfo); 213 DWORD err; 214 215 if (dirhandle == INVALID_HANDLE_VALUE) { 216 err = GetLastError(); 217 // if the directory was empty we succeeded even though FindFirstFile failed 218 return (err == ERROR_FILE_NOT_FOUND); 219 } 220 221 do { 222 std::string fname = transcode_utf16_to_utf8(findinfo.cFileName, wcslen(findinfo.cFileName)); 223 if (fname != "." && fname != "..") { 224 const FileInfo::FileType ty = file_type_for_attributes(findinfo.dwFileAttributes); 225 const Time::DateTime modtime = datetime_for_filetime(findinfo.ftLastWriteTime); 226 output.push_back(MakeFileInfo(JoinPath(dirpath, fname), ty, modtime)); 227 } 228 229 if (!FindNextFileW(dirhandle, &findinfo)) { 230 err = GetLastError(); 231 } else 232 err = ERROR_SUCCESS; 233 } while (err == ERROR_SUCCESS); 234 FindClose(dirhandle); 235 236 if (err != ERROR_NO_MORE_FILES) { 237 output.resize(output_head_size); 238 return false; 239 } 240 241 std::sort(output.begin() + output_head_size, output.end()); 242 return true; 243 } 244 make_directory_raw(std::wstring path)245 static bool make_directory_raw(std::wstring path) 246 { 247 // allow multiple tries (to build parent directories) 248 while (true) { 249 if (!CreateDirectoryW(path.c_str(), 0)) { 250 DWORD err = GetLastError(); 251 if (err == ERROR_ALREADY_EXISTS) { 252 DWORD attrs = GetFileAttributesW(path.c_str()); 253 return ((attrs & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY); 254 } else if (err == ERROR_PATH_NOT_FOUND) { 255 if (!PathRemoveFileSpecW(&path[0])) 256 return false; 257 path.resize(wcslen(&path[0])); 258 if (!path.empty() || !make_directory_raw(path)) { 259 return false; 260 } 261 } else { 262 Output("error while attempting to create directory '%s'\n", transcode_utf16_to_utf8(path).c_str()); 263 return false; 264 } 265 } 266 } 267 } 268 MakeDirectory(const std::string & path)269 bool FileSourceFS::MakeDirectory(const std::string &path) 270 { 271 const std::string fullpath = JoinPathBelow(GetRoot(), path); 272 const std::wstring wfullpath = transcode_utf8_to_utf16(fullpath); 273 return make_directory_raw(wfullpath); 274 } 275 open_file_raw(const std::string & fullpath,const wchar_t * mode)276 static FILE *open_file_raw(const std::string &fullpath, const wchar_t *mode) 277 { 278 const std::wstring wfullpath = transcode_utf8_to_utf16(fullpath); 279 return _wfopen(wfullpath.c_str(), mode); 280 } 281 OpenReadStream(const std::string & path)282 FILE *FileSourceFS::OpenReadStream(const std::string &path) 283 { 284 const std::string fullpath = JoinPathBelow(GetRoot(), path); 285 return open_file_raw(fullpath, L"rb"); 286 } 287 OpenWriteStream(const std::string & path,int flags)288 FILE *FileSourceFS::OpenWriteStream(const std::string &path, int flags) 289 { 290 const std::string fullpath = JoinPathBelow(GetRoot(), path); 291 return open_file_raw(fullpath, (flags & WRITE_TEXT) ? L"w" : L"wb"); 292 } 293 } // namespace FileSystem 294