1 #ifdef HAVE_CONFIG_H
2 #include "config.h"
3 #endif
4
5 #define _GNU_SOURCE 1
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <string.h>
9 #include <limits.h>
10 #include <uae/uae.h>
11 #include <fs/base.h>
12 #include <fs/emu.h>
13 #include "fs-uae.h"
14 #include "config-common.h"
15 #include "paths.h"
16 #include "options.h"
17
18 struct multipath g_paths[5] = {};
19 static char *g_fs_uae_state_base_name;
20
fs_uae_home_dir()21 static const char* fs_uae_home_dir()
22 {
23 static const char *path;
24 if (!path) {
25 path = fs_get_home_dir();
26 if (path == NULL) {
27 fs_log("WARNING: did not find home directory\n");
28 path = g_strdup("");
29 }
30 fs_log("- using home directory \"%s\"\n", path);
31 }
32 return path;
33 }
34
fs_uae_exe_dir()35 const char* fs_uae_exe_dir()
36 {
37 static const char *path;
38 if (!path) {
39 char *app_dir = (char*) malloc(MAX_PATH);
40 fs_get_application_exe_dir(app_dir, MAX_PATH);
41 char *p = g_strdup(app_dir);
42 g_free(app_dir);
43 path = p;
44 fs_log("- using $exe directory \"%s\"\n", path);
45 }
46 return path;
47 }
48
fs_uae_app_dir()49 static const char* fs_uae_app_dir()
50 {
51 static const char *path;
52 if (!path) {
53 char *app_dir = (char*) malloc(MAX_PATH);
54 fs_get_application_exe_dir(app_dir, MAX_PATH);
55 char *p = g_strdup(app_dir);
56 g_free(app_dir);
57 path = p;
58 #ifdef MACOSX
59 char *base_name = g_path_get_basename(p);
60 if (strcmp(base_name, "MacOS") == 0) {
61 char *temp;
62 temp = p;
63 p = g_path_get_dirname(p);
64 g_free(temp);
65 temp = p;
66 p = g_path_get_dirname(p);
67 g_free(temp);
68 temp = p;
69 p = g_path_get_dirname(p);
70 g_free(temp);
71 path = p;
72 }
73 free(base_name);
74 #endif
75 fs_log("- using $app directory \"%s\"\n", path);
76 }
77 return path;
78 }
79
fs_uae_documents_dir()80 static const char* fs_uae_documents_dir()
81 {
82 static const char *path;
83 if (!path) {
84 path = fs_get_documents_dir();
85 if (path == NULL) {
86 fs_log("WARNING: did not find documents directory\n");
87 path = fs_uae_home_dir();
88 }
89 int result = g_mkdir_with_parents(path, 0755);
90 if (result == -1) {
91 fs_emu_warning("Documents directory does not "
92 "exist: %s", path);
93 path = fs_uae_home_dir();
94 }
95 fs_log("- using documents directory \"%s\"\n", path);
96 }
97 return path;
98 }
99
read_custom_path(const char * key)100 static char* read_custom_path(const char *key)
101 {
102 char *key_path = g_build_filename(fse_user_config_dir(),
103 "fs-uae", key, NULL);
104 fs_log("- checking %s\n", key_path);
105 if (fs_path_is_file(key_path)) {
106 FILE * f = fopen(key_path, "rb");
107 free(key_path);
108 if (f == NULL) {
109 fs_log("- file exists but could not open\n");
110 return NULL;
111 }
112
113 char *buffer = (char *) malloc(PATH_MAX + 1);
114 int read_bytes = fread(buffer, 1, PATH_MAX, f);
115 int eof = feof(f);
116 fclose(f);
117 if (!eof) {
118 fs_log("- did not get EOF\n");
119 free(buffer);
120 return NULL;
121 }
122 buffer[read_bytes] = '\0';
123 g_strchomp(buffer);
124 fs_log("- read from file: %s\n", buffer);
125 char *result = fs_uae_expand_path(buffer);
126 free(buffer);
127 fs_log("- expanded path: %s\n", result);
128 return result;
129 }
130 return NULL;
131 }
132
fs_uae_base_dir(void)133 const char* fs_uae_base_dir(void)
134 {
135 static const char* path;
136 if (path) {
137 return path;
138 }
139
140 path = fs_config_get_const_string("base_dir");
141 if (path) {
142 fs_log("base specified via base_dir option\n");
143 path = fs_uae_expand_path(path);
144 }
145 if (path == NULL) {
146 // FIXME: deprecated
147 const char *env_path = getenv("FS_UAE_BASE_DIR");
148 if (env_path && env_path[0]) {
149 path = env_path;
150 fs_log("base specified via FS_UAE_BASE_DIR\n");
151 fs_emu_deprecated("FS_UAE_BASE_DIR is deprecated");
152 }
153 }
154 if (path == NULL) {
155 // check for portable dir
156 char buffer[MAX_PATH];
157 fs_get_application_exe_dir(buffer, MAX_PATH);
158 char *next = g_strdup(buffer);
159 char *orig = g_strdup(":INVALID;");
160 while (strcmp(orig, next) != 0) {
161 char *test = g_build_filename(next, "Portable.ini", NULL);
162 fs_log("checking %s\n", test);
163 if (fs_path_exists(test)) {
164 path = next;
165 fs_log("using portable base dir %s\n", path);
166 g_free(orig);
167 break;
168 }
169 g_free(test);
170 g_free(orig);
171 orig = next;
172 next = g_path_get_dirname(next);
173 }
174 }
175 if (path == NULL) {
176 path = read_custom_path("base-dir");
177 }
178 if (path == NULL) {
179 fs_log("- using base dir $DOCUMENTS/FS-UAE\n");
180 path = g_build_filename(fs_uae_documents_dir(), "FS-UAE", NULL);
181 }
182
183 int result = g_mkdir_with_parents(path, 0755);
184 if (result == -1) {
185 fs_emu_warning("Could not create base directory "
186 "at %s", path);
187 path = fs_uae_documents_dir();
188 }
189 fs_log("- using base ($BASE / $FSUAE) directory \"%s\"\n", path);
190 return path;
191 }
192
get_or_create_default_dir(const char * name,const char * key1,const char * key2,const char * dashed_key,int create,int cache)193 static char *get_or_create_default_dir(const char *name, const char *key1,
194 const char *key2, const char *dashed_key, int create, int cache)
195 {
196 char *path = NULL;
197
198 if (path == NULL && key1 != NULL) {
199 path = fs_config_get_string(key1);
200 }
201 if (path == NULL && key2 != NULL) {
202 path = fs_config_get_string(key2);
203 }
204 if (path == NULL && dashed_key != NULL) {
205 path = read_custom_path(dashed_key);
206 }
207 if (path == NULL) {
208 if (cache) {
209 path = g_build_filename(fs_uae_cache_dir(), name, NULL);
210 } else {
211 path = g_build_filename(fs_uae_base_dir(), name, NULL);
212 }
213 }
214 char *expanded_path = fs_uae_expand_path_and_free(path);
215 path = fs_uae_resolve_path(expanded_path, FS_UAE_DIR_PATHS);
216 free(expanded_path);
217
218 if (create) {
219 int result = g_mkdir_with_parents(path, 0755);
220 if (result == -1) {
221 fs_emu_warning("Could not create %s directory", name);
222 free(path);
223 path = g_strdup(fs_uae_base_dir());
224 }
225 }
226 fs_log("- using \"%s\" directory \"%s\"\n", name, path);
227 return path;
228 }
229
create_default_cache_dir(const char * name,const char * key1,const char * key2,const char * dashed_key)230 static char *create_default_cache_dir(const char *name, const char *key1,
231 const char *key2, const char *dashed_key)
232 {
233 return get_or_create_default_dir(name, key1, key2, dashed_key, 1, 1);
234 }
235
create_default_dir(const char * name,const char * key1,const char * key2,const char * dashed_key)236 static char *create_default_dir(const char *name, const char *key1,
237 const char *key2, const char *dashed_key)
238 {
239 return get_or_create_default_dir(name, key1, key2, dashed_key, 1, 0);
240 }
241
get_default_dir(const char * name,const char * key1,const char * key2,const char * dashed_key)242 static char *get_default_dir(const char *name, const char *key1,
243 const char *key2, const char *dashed_key)
244 {
245 return get_or_create_default_dir(name, key1, key2, dashed_key, 0, 0);
246 }
247
fs_uae_kickstarts_dir()248 const char *fs_uae_kickstarts_dir()
249 {
250 static const char *path;
251 if (!path) {
252 path = create_default_dir("Kickstarts", "kickstarts_dir", "roms_dir",
253 "kickstarts-dir");
254 }
255 return path;
256 }
257
fs_uae_configurations_dir()258 const char *fs_uae_configurations_dir()
259 {
260 static const char *path;
261 if (!path) {
262 path = create_default_dir("Configurations", "configurations_dir",
263 NULL, "configurations-dir");
264 }
265 return path;
266 }
267
fs_uae_save_states_dir()268 static const char *fs_uae_save_states_dir()
269 {
270 static const char *path;
271 if (!path) {
272 path = create_default_dir("Save States", "save_states_dir",
273 NULL, "save-states-dir");
274 }
275 return path;
276 }
277
fs_uae_state_dir_path()278 static const char *fs_uae_state_dir_path()
279 {
280 char *free_state_dir_name = NULL;
281 static const char *path;
282 if (!path) {
283 fs_log("fs_uae_state_dir:\n");
284 path = fs_config_get_const_string("state_dir");
285 if (path && path[0]) {
286 fs_log("state_dir was explicitly set to: %s\n", path);
287 char *expanded_path = fs_uae_expand_path(path);
288 path = fs_uae_resolve_path(expanded_path, FS_UAE_DIR_PATHS);
289 free(expanded_path);
290 return path;
291 }
292 const char *base = fs_uae_save_states_dir();
293 fs_log("save_states_dir: %s\n", base);
294 const char *state_dir_name = fs_config_get_const_string(
295 "state_dir_name");
296 if (!state_dir_name || !state_dir_name[0]) {
297 if (g_fs_uae_config_file_path) {
298 char *n = g_path_get_basename(g_fs_uae_config_file_path);
299 for (int i = strlen(n) - 1; i >= 0; i--) {
300 if (n[i] == '.') {
301 n[i] = '\0';
302 break;
303 }
304 }
305 free_state_dir_name = g_strdup(n);
306 state_dir_name = free_state_dir_name;
307 g_free(n);
308 }
309 if (!state_dir_name || !state_dir_name[0]) {
310 state_dir_name = "Default";
311 }
312 fs_log("save_dir_name not set, using %s\n", state_dir_name);
313 }
314 path = g_build_filename(base, state_dir_name, NULL);
315 }
316 if (free_state_dir_name) {
317 g_free(free_state_dir_name);
318 }
319 return path;
320 }
321
fs_uae_state_dir()322 const char *fs_uae_state_dir()
323 {
324 static const char *path;
325 if (!path) {
326 path = fs_uae_state_dir_path();
327 if (!path || !path[0]) {
328 path = fs_uae_base_dir();
329 fs_log("reverting state dir to: %s\n", path);
330 }
331 fs_log("- using state dir %s\n", path);
332 int result = g_mkdir_with_parents(path, 0755);
333 if (result == -1) {
334 fs_emu_warning("Could not create state directory");
335 path = fs_uae_base_dir();
336 }
337 fs_log("final state dir path: %s\n", path);
338 }
339 return path;
340 }
341
fs_uae_cdroms_dir()342 const char *fs_uae_cdroms_dir()
343 {
344 static const char *path;
345 if (!path) {
346 path = create_default_dir("CD-ROMs", "cdroms_dir", NULL,
347 "cdroms-dir");
348 }
349 return path;
350 }
351
fs_uae_hard_drives_dir()352 const char *fs_uae_hard_drives_dir()
353 {
354 static const char *path;
355 if (!path) {
356 path = create_default_dir("Hard Drives", "hard_drives_dir", NULL,
357 "hard-drives-dir");
358 }
359 return path;
360 }
361
fs_uae_floppies_dir()362 const char *fs_uae_floppies_dir()
363 {
364 static const char *path;
365 if (!path) {
366 path = create_default_dir("Floppies", "floppies_dir", NULL,
367 "floppies-dir");
368 }
369 return path;
370 }
371
fs_uae_controllers_dir()372 const char *fs_uae_controllers_dir()
373 {
374 static const char *path;
375 if (!path) {
376 path = create_default_dir("Controllers", "controllers_dir", NULL,
377 "controllers-dir");
378 }
379 return path;
380 }
381
fs_uae_logs_dir()382 const char *fs_uae_logs_dir()
383 {
384 static const char *path;
385 if (!path) {
386 path = create_default_cache_dir("Logs", "logs_dir", NULL, "logs-dir");
387 }
388 return path;
389 }
390
fs_uae_module_ripper_dir()391 const char *fs_uae_module_ripper_dir()
392 {
393 static const char *path;
394 if (!path) {
395 path = create_default_cache_dir("Modules", "module_ripper_dir", NULL,
396 "module-ripper-dir");
397 }
398 return path;
399 }
400
fs_uae_cache_dir(void)401 const char *fs_uae_cache_dir(void)
402 {
403 static const char *path;
404 if (!path) {
405 path = create_default_dir("Cache", "cache_dir", NULL, "cache-dir");
406 }
407 return path;
408 }
409
fs_uae_data_dir(void)410 const char *fs_uae_data_dir(void)
411 {
412 static const char *path;
413 if (!path) {
414 path = create_default_dir("Data", "data_dir", NULL, "data-dir");
415 }
416 return path;
417 }
418
fs_uae_kickstarts_cache_dir()419 const char *fs_uae_kickstarts_cache_dir()
420 {
421 static const char *path;
422 if (!path) {
423 path = g_build_filename(fs_uae_cache_dir(), "Kickstarts", NULL);
424 int result = g_mkdir_with_parents(path, 0755);
425 if (result == -1) {
426 fs_emu_warning("Could not create kickstarts cache directory");
427 path = fs_uae_base_dir();
428 }
429 }
430 return path;
431 }
432
fs_uae_themes_dir()433 const char *fs_uae_themes_dir()
434 {
435 static const char *path;
436 if (!path) {
437 path = create_default_dir("Themes", "themes_dir", NULL,
438 "themes-dir");
439 }
440 return path;
441 }
442
fs_uae_plugins_dir(void)443 const char *fs_uae_plugins_dir(void)
444 {
445 static const char *path;
446 if (!path) {
447 path = get_default_dir("Plugins", "plugins_dir", NULL,
448 "plugins-dir");
449 }
450 return path;
451 }
452
fs_uae_system_dir(void)453 const char *fs_uae_system_dir(void)
454 {
455 static const char *path;
456 if (!path) {
457 path = get_default_dir("System", "system_dir", NULL,
458 "system-dir");
459 }
460 return path;
461 }
462
fs_uae_temp_dir(void)463 static const char *fs_uae_temp_dir(void)
464 {
465 static const char *path = NULL;
466 if (path == NULL) {
467 path = g_dir_make_tmp("fs-uae-TEMP-XXXXXX", NULL);
468 fs_log("FS-UAE $TEMP = %s\n", path);
469 }
470 return path;
471 }
472
check_path_prefix(const char * path,const char * prefix,int * r)473 static bool check_path_prefix(const char *path, const char *prefix, int *r)
474 {
475 if (g_str_has_prefix(path, prefix)) {
476 *r = strlen(prefix);
477 if (path[*r] == '/' || path[*r] == '\\') {
478 *r += 1;
479 return true;
480 } else if (path[*r] == '\0') {
481 return true;
482 }
483 }
484 return false;
485 }
486
fs_uae_expand_path(const char * path)487 char *fs_uae_expand_path(const char* path)
488 {
489 char* lower = g_ascii_strdown(path, -1);
490 int replace = 0;
491 const char *replace_with = NULL;
492
493 if (check_path_prefix(lower, "~", &replace)) {
494 replace_with = fs_uae_home_dir();
495 } else if (check_path_prefix(lower, "$home", &replace)) {
496 replace_with = fs_uae_home_dir();
497 } else if (check_path_prefix(lower, "$app", &replace)) {
498 replace_with = fs_uae_app_dir();
499 } else if (check_path_prefix(lower, "$exe", &replace)) {
500 replace_with = fs_uae_exe_dir();
501 } else if (check_path_prefix(lower, "$fsuae", &replace)) {
502 replace_with = fs_uae_base_dir();
503 } else if (check_path_prefix(lower, "$base", &replace)) {
504 replace_with = fs_uae_base_dir();
505 } else if (check_path_prefix(lower, "$documents", &replace)) {
506 replace_with = fs_uae_documents_dir();
507 } else if (check_path_prefix(lower, "$config", &replace)) {
508 replace_with = g_fs_uae_config_dir_path;
509 } else if (check_path_prefix(lower, "$temp", &replace)) {
510 replace_with = fs_uae_temp_dir();
511 }
512
513 free(lower);
514 if (replace_with) {
515 const char *src = path + replace;
516 return g_build_filename(replace_with, src, NULL);
517 } else {
518 return g_strdup(path);
519 }
520 }
521
fs_uae_expand_path_and_free(char * path)522 char *fs_uae_expand_path_and_free(char *path)
523 {
524 char *p = fs_uae_expand_path(path);
525 free(path);
526 return p;
527 }
528
fs_uae_get_state_base_name()529 const char *fs_uae_get_state_base_name()
530 {
531 return g_fs_uae_state_base_name;
532 }
533
fs_uae_set_state_base_name(const char * base_name)534 void fs_uae_set_state_base_name(const char *base_name)
535 {
536 g_fs_uae_state_base_name = g_strdup(base_name);
537 }
538
fix_separators(char * path)539 static void fix_separators(char *path)
540 {
541 #ifdef WINDOWS
542 char *p = path;
543 while (*p) {
544 if (*p == '\\') {
545 *p = '/';
546 }
547 p++;
548 }
549 #endif
550 }
551
fs_uae_resolve_path(const char * name,int type)552 char *fs_uae_resolve_path(const char *name, int type)
553 {
554 if (name[0] == '\0') {
555 fs_log("resolve_path (empty string)\n");
556 return g_strdup("");
557 }
558 else if (g_path_is_absolute(name)) {
559 fs_log("resolve_path %s (absolute)\n", name);
560 char *path = g_strdup(name);
561 fix_separators(path);
562 return path;
563 }
564 else {
565 fs_log("resolve_path %s (relative)\n", name);
566 for (int i = 0; i < MAX_PATHS; i++) {
567 if (!g_paths[type].path[i] || g_paths[type].path[i][0] == '\0') {
568 continue;
569 }
570 char *path = g_build_filename(g_paths[type].path[i], name, NULL);
571 fs_log("checking %s\n", path);
572 if (fs_path_exists(path)) {
573 fs_log("- found %s\n", path);
574 fix_separators(path);
575 return path;
576 }
577 free(path);
578 }
579 }
580 fs_log("WARNING: did not find path\n", name);
581 char *path = g_strdup(name);
582 fix_separators(path);
583 return path;
584 }
585
fs_uae_resolve_path_and_free(char * name,int type)586 char *fs_uae_resolve_path_and_free(char *name, int type)
587 {
588 char *result = fs_uae_resolve_path(name, type);
589 free(name);
590 return result;
591 }
592
fs_uae_init_path_resolver(void)593 void fs_uae_init_path_resolver(void)
594 {
595 bool relative_paths = true;
596 if (fs_config_false(OPTION_RELATIVE_PATHS)) {
597 relative_paths = false;
598 }
599
600 int k;
601 k = 0;
602 if (relative_paths) {
603 /* Current working directory should always come first (index 0) */
604 g_paths[FS_UAE_DIR_PATHS].path[k++] = g_strdup(".");
605 }
606 if (g_fs_uae_config_dir_path) {
607 g_paths[FS_UAE_DIR_PATHS].path[k++] = g_strdup(
608 g_fs_uae_config_dir_path);
609 }
610
611 k = 0;
612 if (relative_paths) {
613 g_paths[FS_UAE_FLOPPY_PATHS].path[k++] = g_strdup(".");
614 }
615 if (g_fs_uae_config_dir_path) {
616 g_paths[FS_UAE_FLOPPY_PATHS].path[k++] = g_strdup(
617 g_fs_uae_config_dir_path);
618 }
619 if (fs_uae_floppies_dir()) {
620 g_paths[FS_UAE_FLOPPY_PATHS].path[k++] = g_strdup(
621 fs_uae_floppies_dir());
622 }
623
624 k = 0;
625 if (relative_paths) {
626 g_paths[FS_UAE_CD_PATHS].path[k++] = g_strdup(".");
627 }
628 if (g_fs_uae_config_dir_path) {
629 g_paths[FS_UAE_CD_PATHS].path[k++] = g_strdup(
630 g_fs_uae_config_dir_path);
631 }
632 if (fs_uae_cdroms_dir()) {
633 g_paths[FS_UAE_CD_PATHS].path[k++] = g_strdup(
634 fs_uae_cdroms_dir());
635 }
636
637 k = 0;
638 if (relative_paths) {
639 g_paths[FS_UAE_HD_PATHS].path[k++] = g_strdup(".");
640 }
641 if (g_fs_uae_config_dir_path) {
642 g_paths[FS_UAE_HD_PATHS].path[k++] = g_strdup(
643 g_fs_uae_config_dir_path);
644 }
645 if (fs_uae_hard_drives_dir()) {
646 g_paths[FS_UAE_HD_PATHS].path[k++] = g_strdup(
647 fs_uae_hard_drives_dir());
648
649 }
650
651 k = 0;
652 if (relative_paths) {
653 g_paths[FS_UAE_ROM_PATHS].path[k++] = g_strdup(".");
654 }
655 if (g_fs_uae_config_dir_path) {
656 g_paths[FS_UAE_ROM_PATHS].path[k++] = g_strdup(
657 g_fs_uae_config_dir_path);
658 }
659 if (fs_uae_kickstarts_dir()) {
660 g_paths[FS_UAE_ROM_PATHS].path[k++] = g_strdup(
661 fs_uae_kickstarts_dir());
662 }
663 }
664