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