1/**************************************************************************/
2/* dir_access_windows.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#if defined(WINDOWS_ENABLED)
32
33#include "dir_access_windows.h"
34
35#include "core/os/memory.h"
36#include "core/string/print_string.h"
37
38#include <stdio.h>
39#include <wchar.h>
40#define WIN32_LEAN_AND_MEAN
41#include <windows.h>
42
43struct DirAccessWindowsPrivate {
44 HANDLE h; // handle for FindFirstFile.
45 WIN32_FIND_DATA f;
46 WIN32_FIND_DATAW fu; // Unicode version.
47};
48
49String DirAccessWindows::fix_path(String p_path) const {
50 String r_path = DirAccess::fix_path(p_path);
51 if (r_path.is_absolute_path() && !r_path.is_network_share_path() && r_path.length() > MAX_PATH) {
52 r_path = "\\\\?\\" + r_path.replace("/", "\\");
53 }
54 return r_path;
55}
56
57// CreateFolderAsync
58
59Error DirAccessWindows::list_dir_begin() {
60 _cisdir = false;
61 _cishidden = false;
62
63 list_dir_end();
64 p->h = FindFirstFileExW((LPCWSTR)(String(current_dir + "\\*").utf16().get_data()), FindExInfoStandard, &p->fu, FindExSearchNameMatch, nullptr, 0);
65
66 if (p->h == INVALID_HANDLE_VALUE) {
67 return ERR_CANT_OPEN;
68 }
69
70 return OK;
71}
72
73String DirAccessWindows::get_next() {
74 if (p->h == INVALID_HANDLE_VALUE) {
75 return "";
76 }
77
78 _cisdir = (p->fu.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
79 _cishidden = (p->fu.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN);
80
81 String name = String::utf16((const char16_t *)(p->fu.cFileName));
82
83 if (FindNextFileW(p->h, &p->fu) == 0) {
84 FindClose(p->h);
85 p->h = INVALID_HANDLE_VALUE;
86 }
87
88 return name;
89}
90
91bool DirAccessWindows::current_is_dir() const {
92 return _cisdir;
93}
94
95bool DirAccessWindows::current_is_hidden() const {
96 return _cishidden;
97}
98
99void DirAccessWindows::list_dir_end() {
100 if (p->h != INVALID_HANDLE_VALUE) {
101 FindClose(p->h);
102 p->h = INVALID_HANDLE_VALUE;
103 }
104}
105
106int DirAccessWindows::get_drive_count() {
107 return drive_count;
108}
109
110String DirAccessWindows::get_drive(int p_drive) {
111 if (p_drive < 0 || p_drive >= drive_count) {
112 return "";
113 }
114
115 return String::chr(drives[p_drive]) + ":";
116}
117
118Error DirAccessWindows::change_dir(String p_dir) {
119 GLOBAL_LOCK_FUNCTION
120
121 p_dir = fix_path(p_dir);
122
123 WCHAR real_current_dir_name[2048];
124 GetCurrentDirectoryW(2048, real_current_dir_name);
125 String prev_dir = String::utf16((const char16_t *)real_current_dir_name);
126
127 SetCurrentDirectoryW((LPCWSTR)(current_dir.utf16().get_data()));
128 bool worked = (SetCurrentDirectoryW((LPCWSTR)(p_dir.utf16().get_data())) != 0);
129
130 String base = _get_root_path();
131 if (!base.is_empty()) {
132 GetCurrentDirectoryW(2048, real_current_dir_name);
133 String new_dir = String::utf16((const char16_t *)real_current_dir_name).replace("\\", "/");
134 if (!new_dir.begins_with(base)) {
135 worked = false;
136 }
137 }
138
139 if (worked) {
140 GetCurrentDirectoryW(2048, real_current_dir_name);
141 current_dir = String::utf16((const char16_t *)real_current_dir_name);
142 current_dir = current_dir.replace("\\", "/");
143 }
144
145 SetCurrentDirectoryW((LPCWSTR)(prev_dir.utf16().get_data()));
146
147 return worked ? OK : ERR_INVALID_PARAMETER;
148}
149
150Error DirAccessWindows::make_dir(String p_dir) {
151 GLOBAL_LOCK_FUNCTION
152
153 p_dir = fix_path(p_dir);
154 if (p_dir.is_relative_path()) {
155 p_dir = current_dir.path_join(p_dir);
156 p_dir = fix_path(p_dir);
157 }
158
159 p_dir = p_dir.simplify_path().replace("/", "\\");
160
161 bool success;
162 int err;
163
164 success = CreateDirectoryW((LPCWSTR)(p_dir.utf16().get_data()), nullptr);
165 err = GetLastError();
166
167 if (success) {
168 return OK;
169 }
170
171 if (err == ERROR_ALREADY_EXISTS || err == ERROR_ACCESS_DENIED) {
172 return ERR_ALREADY_EXISTS;
173 }
174
175 return ERR_CANT_CREATE;
176}
177
178String DirAccessWindows::get_current_dir(bool p_include_drive) const {
179 String base = _get_root_path();
180 if (!base.is_empty()) {
181 String bd = current_dir.replace("\\", "/").replace_first(base, "");
182 if (bd.begins_with("/")) {
183 return _get_root_string() + bd.substr(1, bd.length());
184 } else {
185 return _get_root_string() + bd;
186 }
187 }
188
189 if (p_include_drive) {
190 return current_dir;
191 } else {
192 if (_get_root_string().is_empty()) {
193 int pos = current_dir.find(":");
194 if (pos != -1) {
195 return current_dir.substr(pos + 1);
196 }
197 }
198 return current_dir;
199 }
200}
201
202bool DirAccessWindows::file_exists(String p_file) {
203 GLOBAL_LOCK_FUNCTION
204
205 if (!p_file.is_absolute_path()) {
206 p_file = get_current_dir().path_join(p_file);
207 }
208
209 p_file = fix_path(p_file);
210
211 DWORD fileAttr;
212
213 fileAttr = GetFileAttributesW((LPCWSTR)(p_file.utf16().get_data()));
214 if (INVALID_FILE_ATTRIBUTES == fileAttr) {
215 return false;
216 }
217
218 return !(fileAttr & FILE_ATTRIBUTE_DIRECTORY);
219}
220
221bool DirAccessWindows::dir_exists(String p_dir) {
222 GLOBAL_LOCK_FUNCTION
223
224 if (p_dir.is_relative_path()) {
225 p_dir = get_current_dir().path_join(p_dir);
226 }
227
228 p_dir = fix_path(p_dir);
229
230 DWORD fileAttr;
231 fileAttr = GetFileAttributesW((LPCWSTR)(p_dir.utf16().get_data()));
232 if (INVALID_FILE_ATTRIBUTES == fileAttr) {
233 return false;
234 }
235 return (fileAttr & FILE_ATTRIBUTE_DIRECTORY);
236}
237
238Error DirAccessWindows::rename(String p_path, String p_new_path) {
239 if (p_path.is_relative_path()) {
240 p_path = get_current_dir().path_join(p_path);
241 }
242
243 p_path = fix_path(p_path);
244
245 if (p_new_path.is_relative_path()) {
246 p_new_path = get_current_dir().path_join(p_new_path);
247 }
248
249 p_new_path = fix_path(p_new_path);
250
251 // If we're only changing file name case we need to do a little juggling
252 if (p_path.to_lower() == p_new_path.to_lower()) {
253 if (dir_exists(p_path)) {
254 // The path is a dir; just rename
255 return ::_wrename((LPCWSTR)(p_path.utf16().get_data()), (LPCWSTR)(p_new_path.utf16().get_data())) == 0 ? OK : FAILED;
256 }
257 // The path is a file; juggle
258 WCHAR tmpfile[MAX_PATH];
259
260 if (!GetTempFileNameW((LPCWSTR)(fix_path(get_current_dir()).utf16().get_data()), nullptr, 0, tmpfile)) {
261 return FAILED;
262 }
263
264 if (!::ReplaceFileW(tmpfile, (LPCWSTR)(p_path.utf16().get_data()), nullptr, 0, nullptr, nullptr)) {
265 DeleteFileW(tmpfile);
266 return FAILED;
267 }
268
269 return ::_wrename(tmpfile, (LPCWSTR)(p_new_path.utf16().get_data())) == 0 ? OK : FAILED;
270
271 } else {
272 if (file_exists(p_new_path)) {
273 if (remove(p_new_path) != OK) {
274 return FAILED;
275 }
276 }
277
278 return ::_wrename((LPCWSTR)(p_path.utf16().get_data()), (LPCWSTR)(p_new_path.utf16().get_data())) == 0 ? OK : FAILED;
279 }
280}
281
282Error DirAccessWindows::remove(String p_path) {
283 if (p_path.is_relative_path()) {
284 p_path = get_current_dir().path_join(p_path);
285 }
286
287 p_path = fix_path(p_path);
288
289 DWORD fileAttr;
290
291 fileAttr = GetFileAttributesW((LPCWSTR)(p_path.utf16().get_data()));
292 if (INVALID_FILE_ATTRIBUTES == fileAttr) {
293 return FAILED;
294 }
295 if ((fileAttr & FILE_ATTRIBUTE_DIRECTORY)) {
296 return ::_wrmdir((LPCWSTR)(p_path.utf16().get_data())) == 0 ? OK : FAILED;
297 } else {
298 return ::_wunlink((LPCWSTR)(p_path.utf16().get_data())) == 0 ? OK : FAILED;
299 }
300}
301
302uint64_t DirAccessWindows::get_space_left() {
303 uint64_t bytes = 0;
304 if (!GetDiskFreeSpaceEx(nullptr, (PULARGE_INTEGER)&bytes, nullptr, nullptr)) {
305 return 0;
306 }
307
308 // This is either 0 or a value in bytes.
309 return bytes;
310}
311
312String DirAccessWindows::get_filesystem_type() const {
313 String path = fix_path(const_cast<DirAccessWindows *>(this)->get_current_dir());
314
315 int unit_end = path.find(":");
316 ERR_FAIL_COND_V(unit_end == -1, String());
317 String unit = path.substr(0, unit_end + 1) + "\\";
318
319 if (path.is_network_share_path()) {
320 return "Network Share";
321 }
322
323 WCHAR szVolumeName[100];
324 WCHAR szFileSystemName[10];
325 DWORD dwSerialNumber = 0;
326 DWORD dwMaxFileNameLength = 0;
327 DWORD dwFileSystemFlags = 0;
328
329 if (::GetVolumeInformationW((LPCWSTR)(unit.utf16().get_data()),
330 szVolumeName,
331 sizeof(szVolumeName),
332 &dwSerialNumber,
333 &dwMaxFileNameLength,
334 &dwFileSystemFlags,
335 szFileSystemName,
336 sizeof(szFileSystemName)) == TRUE) {
337 return String::utf16((const char16_t *)szFileSystemName);
338 }
339
340 ERR_FAIL_V("");
341}
342
343DirAccessWindows::DirAccessWindows() {
344 p = memnew(DirAccessWindowsPrivate);
345 p->h = INVALID_HANDLE_VALUE;
346 current_dir = ".";
347
348 DWORD mask = GetLogicalDrives();
349
350 for (int i = 0; i < MAX_DRIVES; i++) {
351 if (mask & (1 << i)) { //DRIVE EXISTS
352
353 drives[drive_count] = 'A' + i;
354 drive_count++;
355 }
356 }
357
358 change_dir(".");
359}
360
361DirAccessWindows::~DirAccessWindows() {
362 list_dir_end();
363
364 memdelete(p);
365}
366
367#endif // WINDOWS_ENABLED
368