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