Project Alice
Loading...
Searching...
No Matches
simple_fs_win.cpp
Go to the documentation of this file.
1#include "simple_fs.hpp"
3#include "text.hpp"
4
5#ifndef UNICODE
6#define UNICODE
7#endif
8#define NOMINMAX
9#define WIN32_LEAN_AND_MEAN
10
11#include "Windows.h"
12#include "Memoryapi.h"
13#include "Shlobj.h"
14#include <cstdlib>
15
16#pragma comment(lib, "Shlwapi.lib")
17
18namespace simple_fs {
20 if(mapping_handle) {
21 if(content.data)
22 UnmapViewOfFile(content.data);
23 CloseHandle(mapping_handle);
24 }
25 if(file_handle != INVALID_HANDLE_VALUE) {
26 CloseHandle(file_handle);
27 }
28}
29
30file::file(file&& other) noexcept : absolute_path(std::move(other.absolute_path)) {
31 mapping_handle = other.mapping_handle;
32 file_handle = other.file_handle;
33 other.mapping_handle = nullptr;
34 other.file_handle = INVALID_HANDLE_VALUE;
35 content = other.content;
36}
37void file::operator=(file&& other) noexcept {
38 mapping_handle = other.mapping_handle;
39 file_handle = other.file_handle;
40 other.mapping_handle = nullptr;
41 other.file_handle = INVALID_HANDLE_VALUE;
42 content = other.content;
43 absolute_path = std::move(other.absolute_path);
44}
45
46file::file(native_string const& full_path) {
47 file_handle = CreateFileW(full_path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING,
48 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
49 if(file_handle != INVALID_HANDLE_VALUE) {
50 absolute_path = full_path;
51 mapping_handle = CreateFileMappingW(file_handle, nullptr, PAGE_READONLY, 0, 0, nullptr);
52 if(mapping_handle) {
53 content.data = (char const*)MapViewOfFile(mapping_handle, FILE_MAP_READ, 0, 0, 0);
54 if(content.data) {
55 _LARGE_INTEGER pvalue;
56 GetFileSizeEx(file_handle, &pvalue);
57 content.file_size = uint32_t(pvalue.QuadPart);
58 }
59 }
60 }
61}
62file::file(HANDLE file_handle, native_string const& full_path) : file_handle(file_handle) {
63 absolute_path = full_path;
64 mapping_handle = CreateFileMappingW(file_handle, nullptr, PAGE_READONLY, 0, 0, nullptr);
65 if(mapping_handle) {
66 content.data = (char const*)MapViewOfFile(mapping_handle, FILE_MAP_READ, 0, 0, 0);
67 if(content.data) {
68 _LARGE_INTEGER pvalue;
69 GetFileSizeEx(file_handle, &pvalue);
70 content.file_size = uint32_t(pvalue.QuadPart);
71 }
72 }
73}
74
75std::optional<file> open_file(unopened_file const& f) {
76 std::optional<file> result(file{f.absolute_path});
77 if(!result->content.data) {
78 result = std::optional<file>{};
79 }
80 return result;
81}
82
83void reset(file_system& fs) {
84 fs.ordered_roots.clear();
85 fs.ignored_paths.clear();
86}
87
88void add_root(file_system& fs, native_string_view root_path) {
89 fs.ordered_roots.emplace_back(root_path);
90}
91
92void add_relative_root(file_system& fs, native_string_view root_path) {
93 WCHAR module_name[MAX_PATH] = {};
94 int32_t path_used = GetModuleFileNameW(nullptr, module_name, MAX_PATH);
95 while(path_used >= 0 && module_name[path_used] != L'\\') {
96 module_name[path_used] = 0;
97 --path_used;
98 }
99
100 fs.ordered_roots.push_back(native_string(module_name) + native_string(root_path));
101}
102
103directory get_root(file_system const& fs) {
104 return directory(&fs, NATIVE(""));
105}
106
107native_string extract_state(file_system const& fs) {
108 native_string result;
109 for(auto const& str : fs.ordered_roots) {
110 result += NATIVE(";") + str;
111 }
112 result += NATIVE("?");
113 for(auto const& replace_path : fs.ignored_paths) {
114 result += replace_path + NATIVE(";");
115 }
116 return result;
117}
118
119void restore_state(file_system& fs, native_string_view data) {
121 auto break_position = std::find(data.data(), data.data() + data.length(), NATIVE('?'));
122 // Parse ordered roots
123 {
124 auto position = data.data() + 1;
125 auto end = break_position;
126 while(position < end) {
127 auto next_semicolon = std::find(position, end, NATIVE(';'));
128 fs.ordered_roots.emplace_back(position, next_semicolon);
129 position = next_semicolon + 1;
130 }
131 }
132 // Replaced paths
133 {
134 auto position = break_position + 1;
135 auto end = data.data() + data.length();
136 while(position < end) {
137 auto next_semicolon = std::find(position, end, NATIVE(';'));
138 fs.ignored_paths.emplace_back(position, next_semicolon);
139 position = next_semicolon + 1;
140 }
141 }
142}
143
144namespace impl {
145bool contains_non_ascii(native_char const* str) {
146 for(auto c = str; *c != 0; ++c) {
147 if(int32_t(*c) > 127 || int32_t(*c) < 0)
148 return true;
149 }
150 return false;
151}
152} // namespace impl
153
154std::vector<unopened_file> list_files(directory const& dir, native_char const* extension) {
155 std::vector<unopened_file> accumulated_results;
156 if(dir.parent_system) {
157 for(size_t i = dir.parent_system->ordered_roots.size(); i-- > 0;) {
158 auto const dir_path = dir.parent_system->ordered_roots[i] + dir.relative_path;
159 auto const appended_path = dir_path + NATIVE("\\*") + extension;
160
161 if(simple_fs::is_ignored_path(*dir.parent_system, appended_path)) {
162 continue;
163 }
164
165 WIN32_FIND_DATAW find_result;
166 auto find_handle = FindFirstFileExW(appended_path.c_str(), FindExInfoBasic, &find_result, FindExSearchNameMatch, NULL, FIND_FIRST_EX_LARGE_FETCH);
167 if(find_handle != INVALID_HANDLE_VALUE) {
168 do {
169 if(!(find_result.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && !impl::contains_non_ascii(find_result.cFileName)) {
170 if(auto search_result = std::find_if(accumulated_results.begin(), accumulated_results.end(),
171 [n = find_result.cFileName](auto const& f) { return f.file_name.compare(n) == 0; });
172 search_result == accumulated_results.end()) {
173
174 accumulated_results.emplace_back(dir.parent_system->ordered_roots[i] + dir.relative_path + NATIVE("\\") +
175 find_result.cFileName,
176 find_result.cFileName);
177 }
178 }
179 } while(FindNextFileW(find_handle, &find_result) != 0);
180 FindClose(find_handle);
181 }
182 }
183 } else {
184 auto const appended_path = dir.relative_path + NATIVE("\\*") + extension;
185 WIN32_FIND_DATAW find_result;
186 auto find_handle = FindFirstFileExW(appended_path.c_str(), FindExInfoBasic, &find_result, FindExSearchNameMatch, NULL, FIND_FIRST_EX_LARGE_FETCH);
187 if(find_handle != INVALID_HANDLE_VALUE) {
188 do {
189 if(!(find_result.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && !impl::contains_non_ascii(find_result.cFileName)) {
190 accumulated_results.emplace_back(dir.relative_path + NATIVE("\\") + find_result.cFileName, find_result.cFileName);
191 }
192 } while(FindNextFileW(find_handle, &find_result) != 0);
193 FindClose(find_handle);
194 }
195 }
196 std::sort(accumulated_results.begin(), accumulated_results.end(), [](unopened_file const& a, unopened_file const& b) {
197 return std::lexicographical_compare(std::begin(a.file_name), std::end(a.file_name), std::begin(b.file_name),
198 std::end(b.file_name),
199 [](native_char const& char1, native_char const& char2) { return tolower(char1) < tolower(char2); });
200 });
201 return accumulated_results;
202}
203std::vector<directory> list_subdirectories(directory const& dir) {
204 std::vector<directory> accumulated_results;
205 if(dir.parent_system) {
206 for(size_t i = dir.parent_system->ordered_roots.size(); i-- > 0;) {
207 auto const dir_path = dir.parent_system->ordered_roots[i] + dir.relative_path;
208 auto const appended_path = dir_path + NATIVE("\\*");
209 if(simple_fs::is_ignored_path(*dir.parent_system, appended_path)) {
210 continue;
211 }
212 WIN32_FIND_DATAW find_result;
213 auto find_handle = FindFirstFileExW(appended_path.c_str(), FindExInfoBasic, &find_result, FindExSearchNameMatch, NULL, FIND_FIRST_EX_LARGE_FETCH);
214 if(find_handle != INVALID_HANDLE_VALUE) {
215 do {
216 if((find_result.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && !impl::contains_non_ascii(find_result.cFileName)) {
217 native_string const rel_name = dir.relative_path + NATIVE("\\") + find_result.cFileName;
218 if(find_result.cFileName[0] != NATIVE('.') &&
219 std::find_if(accumulated_results.begin(), accumulated_results.end(),
220 [&rel_name](auto const& s) { return s.relative_path.compare(rel_name) == 0; }) == accumulated_results.end()) {
221 accumulated_results.emplace_back(dir.parent_system, rel_name);
222 }
223 }
224 } while(FindNextFileW(find_handle, &find_result) != 0);
225 FindClose(find_handle);
226 }
227 }
228 } else {
229 auto const appended_path = dir.relative_path + NATIVE("\\*");
230 WIN32_FIND_DATAW find_result;
231 auto find_handle = FindFirstFileExW(appended_path.c_str(), FindExInfoBasic, &find_result, FindExSearchNameMatch, NULL, FIND_FIRST_EX_LARGE_FETCH);
232 if(find_handle != INVALID_HANDLE_VALUE) {
233 do {
234 if((find_result.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && !impl::contains_non_ascii(find_result.cFileName)) {
235 native_string const rel_name = dir.relative_path + NATIVE("\\") + find_result.cFileName;
236 if(find_result.cFileName[0] != NATIVE('.')) {
237 accumulated_results.emplace_back(nullptr, rel_name);
238 }
239 }
240 } while(FindNextFileW(find_handle, &find_result) != 0);
241 FindClose(find_handle);
242 }
243 }
244 std::sort(accumulated_results.begin(), accumulated_results.end(), [](directory const& a, directory const& b) {
245 return std::lexicographical_compare(std::begin(a.relative_path), std::end(a.relative_path), std::begin(b.relative_path),
246 std::end(b.relative_path),
247 [](native_char const& char1, native_char const& char2) { return tolower(char1) < tolower(char2); });
248 });
249 return accumulated_results;
250}
251
252directory open_directory(directory const& dir, native_string_view directory_name) {
253 return directory(dir.parent_system, dir.relative_path + NATIVE('\\') + native_string(directory_name));
254}
255
256native_string get_full_name(directory const& dir) {
257 return dir.relative_path;
258}
259
260std::optional<native_string> get_path_to_file(directory const& dir, native_string_view file_name) {
261 if(dir.parent_system) {
262 for(size_t i = dir.parent_system->ordered_roots.size(); i-- > 0;) {
263 native_string dir_path = dir.parent_system->ordered_roots[i] + dir.relative_path;
264 native_string full_path = dir_path + NATIVE('\\') + native_string(file_name);
265 return full_path;
266 }
267 } else {
268 native_string full_path = dir.relative_path + NATIVE('\\') + native_string(file_name);
269 return full_path;
270 }
271 return std::optional<native_string>{};
272}
273
274std::optional<file> open_file(directory const& dir, native_string_view file_name) {
275 if(dir.parent_system) {
276 for(size_t i = dir.parent_system->ordered_roots.size(); i-- > 0;) {
277 native_string dir_path = dir.parent_system->ordered_roots[i] + dir.relative_path;
278 native_string full_path = dir_path + NATIVE('\\') + native_string(file_name);
279 if(simple_fs::is_ignored_path(*dir.parent_system, full_path)) {
280 continue;
281 }
282 HANDLE file_handle = CreateFileW(full_path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING,
283 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
284 if(file_handle != INVALID_HANDLE_VALUE) {
285 return std::optional<file>(file(file_handle, full_path));
286 }
287 }
288 } else {
289 native_string full_path = dir.relative_path + NATIVE('\\') + native_string(file_name);
290 HANDLE file_handle = CreateFileW(full_path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING,
291 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
292 if(file_handle != INVALID_HANDLE_VALUE) {
293 return std::optional<file>(file(file_handle, full_path));
294 }
295 }
296 return std::optional<file>{};
297}
298
299std::optional<unopened_file> peek_file(directory const& dir, native_string_view file_name) {
300 if(dir.parent_system) {
301 for(size_t i = dir.parent_system->ordered_roots.size(); i-- > 0;) {
302 native_string dir_path = dir.parent_system->ordered_roots[i] + dir.relative_path;
303 native_string full_path = dir_path + NATIVE('\\') + native_string(file_name);
304 if(simple_fs::is_ignored_path(*dir.parent_system, full_path)) {
305 continue;
306 }
307 DWORD dwAttrib = GetFileAttributesW(full_path.c_str());
308 if(dwAttrib != INVALID_FILE_ATTRIBUTES && !(dwAttrib & FILE_ATTRIBUTE_DIRECTORY)) {
309 return std::optional<unopened_file>(unopened_file(full_path, file_name));
310 }
311 }
312 } else {
313 native_string full_path = dir.relative_path + NATIVE('\\') + native_string(file_name);
314 DWORD dwAttrib = GetFileAttributesW(full_path.c_str());
315 if(dwAttrib != INVALID_FILE_ATTRIBUTES && !(dwAttrib & FILE_ATTRIBUTE_DIRECTORY)) {
316 return std::optional<unopened_file>(unopened_file(full_path, file_name));
317 }
318 }
319 return std::optional<unopened_file>{};
320}
321
322void add_ignore_path(file_system& fs, native_string_view replaced_path) {
323 fs.ignored_paths.emplace_back(correct_slashes(replaced_path));
324}
325
326std::vector<native_string> list_roots(file_system const& fs) {
327 return fs.ordered_roots;
328}
329
330bool is_ignored_path(file_system const& fs, native_string_view path) {
331 for(auto const& replace_path : fs.ignored_paths) {
332 if(path.starts_with(replace_path))
333 return true;
334 }
335 return false;
336}
337
338native_string get_full_name(unopened_file const& f) {
339 return f.absolute_path;
340}
341
342native_string get_file_name(unopened_file const& f) {
343 return f.file_name;
344}
345
346native_string get_full_name(file const& f) {
347 return f.absolute_path;
348}
349
350void write_file(directory const& dir, native_string_view file_name, char const* file_data, uint32_t file_size) {
351 if(dir.parent_system)
352 std::abort();
353
354 native_string full_path = dir.relative_path + NATIVE('\\') + native_string(file_name);
355 HANDLE file_handle = CreateFileW(full_path.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS,
356 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
357 if(file_handle != INVALID_HANDLE_VALUE) {
358 DWORD written_bytes = 0;
359 WriteFile(file_handle, file_data, DWORD(file_size), &written_bytes, nullptr);
360 (void)written_bytes;
361 SetEndOfFile(file_handle);
362 CloseHandle(file_handle);
363 }
364}
365
366file_contents view_contents(file const& f) {
367 return f.content;
368}
369
371 wchar_t* local_path_out = nullptr;
372 native_string base_path;
373 if(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &local_path_out) == S_OK) {
374 base_path = native_string(local_path_out) + NATIVE("\\Project Alice");
375 }
376 CoTaskMemFree(local_path_out);
377 if(base_path.length() > 0) {
378 CreateDirectoryW(base_path.c_str(), nullptr);
379 }
380 return directory(nullptr, base_path);
381}
382
384 wchar_t* local_path_out = nullptr;
385 native_string base_path;
386 if(SHGetKnownFolderPath(FOLDERID_Documents, 0, nullptr, &local_path_out) == S_OK) {
387 base_path = native_string(local_path_out) + NATIVE("\\Project Alice");
388 }
389 CoTaskMemFree(local_path_out);
390 if(base_path.length() > 0) {
391 CreateDirectoryW(base_path.c_str(), nullptr);
392 base_path += NATIVE("\\saved games");
393 CreateDirectoryW(base_path.c_str(), nullptr);
394 }
395 return directory(nullptr, base_path);
396}
397
399 wchar_t* local_path_out = nullptr;
400 native_string base_path;
401 if(SHGetKnownFolderPath(FOLDERID_Documents, 0, nullptr, &local_path_out) == S_OK) {
402 base_path = native_string(local_path_out) + NATIVE("\\Project Alice");
403 }
404 CoTaskMemFree(local_path_out);
405 if(base_path.length() > 0) {
406 CreateDirectoryW(base_path.c_str(), nullptr);
407 }
408 return directory(nullptr, base_path);
409}
410
412 native_char* local_path_out = nullptr;
413 native_string base_path;
414 if(SHGetKnownFolderPath(FOLDERID_Documents, 0, nullptr, &local_path_out) == S_OK) {
415 base_path = native_string(local_path_out) + NATIVE("\\Project Alice");
416 }
417 CoTaskMemFree(local_path_out);
418 if(base_path.length() > 0) {
419 CreateDirectoryW(base_path.c_str(), nullptr);
420 base_path += NATIVE("\\templates");
421 CreateDirectoryW(base_path.c_str(), nullptr);
422 }
423 return directory(nullptr, base_path);
424}
425
426directory get_or_create_oos_directory() {
427 native_char* local_path_out = nullptr;
428 native_string base_path;
429 if(SHGetKnownFolderPath(FOLDERID_Documents, 0, nullptr, &local_path_out) == S_OK) {
430 base_path = native_string(local_path_out) + NATIVE("\\Project Alice");
431 }
432 CoTaskMemFree(local_path_out);
433 if(base_path.length() > 0) {
434 CreateDirectoryW(base_path.c_str(), nullptr);
435 base_path += NATIVE("\\oos");
436 CreateDirectoryW(base_path.c_str(), nullptr);
437 }
438 return directory(nullptr, base_path);
439}
440
442 native_char* local_path_out = nullptr;
443 native_string base_path;
444 if(SHGetKnownFolderPath(FOLDERID_Documents, 0, nullptr, &local_path_out) == S_OK) {
445 base_path = native_string(local_path_out) + NATIVE("\\Project Alice");
446 }
447 CoTaskMemFree(local_path_out);
448 if(base_path.length() > 0) {
449 CreateDirectoryW(base_path.c_str(), nullptr);
450 base_path += NATIVE("\\scenarios");
451 CreateDirectoryW(base_path.c_str(), nullptr);
452 }
453 return directory(nullptr, base_path);
454}
455
457 native_char* local_path_out = nullptr;
458 native_string base_path;
459 if(SHGetKnownFolderPath(FOLDERID_Documents, 0, nullptr, &local_path_out) == S_OK) {
460 base_path = native_string(local_path_out) + NATIVE("\\Project Alice");
461 }
462 CoTaskMemFree(local_path_out);
463 if(base_path.length() > 0) {
464 CreateDirectoryW(base_path.c_str(), nullptr);
465 base_path += NATIVE("\\data_dumps");
466 CreateDirectoryW(base_path.c_str(), nullptr);
467 }
468 return directory(nullptr, base_path);
469}
470
471native_string win1250_to_native(std::string_view data_in) {
473 for(auto ch : data_in) {
475 }
476 return result;
477}
478
479native_string utf8_to_native(std::string_view str) {
480 auto buffer = std::unique_ptr<WCHAR[]>(new WCHAR[str.length() * 2]);
481 auto chars_written = MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, str.data(), int32_t(str.length()), buffer.get(), int32_t(str.length() * 2));
482 return native_string(buffer.get(), size_t(chars_written));
483}
484
485std::string native_to_utf8(native_string_view str) {
486 auto buffer = std::unique_ptr<char[]>(new char[str.length() * 4]);
487 auto chars_written = WideCharToMultiByte(CP_UTF8, 0, str.data(), int32_t(str.length()), buffer.get(), int32_t(str.length() * 4), nullptr, nullptr);
488 return std::string(buffer.get(), size_t(chars_written));
489}
490
491std::string remove_double_backslashes(std::string_view data_in) {
492 std::string res;
493 res.reserve(data_in.size());
494 for(uint32_t i = 0; i < data_in.size(); ++i) {
495 if(data_in[i] == '\\') {
496 res += '\\';
497 if(i + 1 < data_in.size() && data_in[i + 1] == '\\')
498 ++i;
499 } else {
500 res += data_in[i];
501 }
502 }
503 return res;
504}
505
507 native_string res;
508 res.reserve(path.size());
509 for(size_t i = 0; i < path.size(); i++) {
510 res += path[i] == '/' ? '\\' : path[i];
511 }
512 return res;
513}
514} // namespace simple_fs
void operator=(file const &other)=delete
bool contains_non_ascii(native_char const *str)
std::vector< unopened_file > list_files(directory const &dir, native_char const *extension)
void add_root(file_system &fs, native_string_view root_path)
void reset(file_system &fs)
native_string win1250_to_native(std::string_view data_in)
void add_relative_root(file_system &fs, native_string_view root_path)
std::vector< directory > list_subdirectories(directory const &dir)
directory open_directory(directory const &dir, native_string_view directory_name)
void add_ignore_path(file_system &fs, native_string_view replaced_path)
native_string utf8_to_native(std::string_view data_in)
native_string extract_state(file_system const &fs)
directory get_root(file_system const &fs)
void restore_state(file_system &fs, native_string_view data)
void write_file(directory const &dir, native_string_view file_name, char const *file_data, uint32_t file_size)
directory get_or_create_oos_directory()
std::vector< native_string > list_roots(file_system const &fs)
directory get_or_create_templates_directory()
std::string remove_double_backslashes(std::string_view data_in)
std::optional< native_string > get_path_to_file(directory const &dir, native_string_view file_name)
directory get_or_create_settings_directory()
native_string get_full_name(directory const &f)
native_string correct_slashes(native_string_view path)
directory get_or_create_save_game_directory()
std::optional< file > open_file(directory const &dir, native_string_view file_name)
directory get_or_create_root_documents()
directory get_or_create_data_dumps_directory()
bool is_ignored_path(file_system const &fs, native_string_view path)
std::optional< unopened_file > peek_file(directory const &dir, native_string_view file_name)
std::string native_to_utf8(native_string_view data_in)
file_contents view_contents(file const &f)
native_string get_file_name(unopened_file const &f)
directory get_or_create_scenario_directory()
char16_t win1250toUTF16(char in)
Definition: text.cpp:587
char native_char
#define NATIVE(X)
std::string_view native_string_view
std::string native_string
uint uint32_t