1 /**
2 * Compute and store the search paths
3
4 * Copyright (C) 2007, 2008, 2009 Sylvain Beucler
5
6 * This file is part of GNU FreeDink
7
8 * GNU FreeDink is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License as
10 * published by the Free Software Foundation; either version 3 of the
11 * License, or (at your option) any later version.
12
13 * GNU FreeDink is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see
20 * <http://www.gnu.org/licenses/>.
21 */
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26 #include <stdlib.h>
27
28 #include "io_util.h"
29 #include "paths.h"
30 #include "msgbox.h"
31 #include "log.h"
32
33
34 #if defined _WIN32 || defined __WIN32__ || defined __CYGWIN__
35 #define WIN32_LEAN_AND_MEAN
36 #define _WIN32_IE 0x0401
37 #include <windows.h>
38 #include <shlobj.h>
39 #endif
40
41 #include "relocatable.h"
42 #include "progname.h"
43 #include "binreloc.h"
44
45 #include <string.h> /* strdup */
46 #include "canonicalize.h" /* canonicalize_file_name */
47
48 /* basename */
49 #include <libgen.h>
50 #include "dirname.h"
51
52 /* mkdir */
53 #include <sys/stat.h>
54 #include <sys/types.h>
55
56 #include "str_util.h" /* asprintf_append */
57
58
59 static char* defaultpkgdatadir = NULL;
60 static char* pkgdatadir = NULL;
61 static char* exedir = NULL;
62 static char* fallbackdir = NULL;
63 static char* dmoddir = NULL;
64 static char* dmodname = NULL;
65 static char* userappdir = NULL;
66 static char* exefile = NULL;
67
68
paths_init(char * argv0,char * refdir_opt,char * dmoddir_opt)69 void paths_init(char *argv0, char *refdir_opt, char *dmoddir_opt)
70 {
71 char *datadir = NULL;
72 char *refdir = NULL;
73
74 /* relocatable_prog */
75 set_program_name(argv0);
76
77 /** default pkgdatadir (e.g. "/usr/share/freedink") **/
78 {
79 defaultpkgdatadir = br_build_path(DEFAULT_DATA_DIR, PACKAGE);
80 }
81
82 /** datadir (e.g. "/usr/share") **/
83 {
84 const char *datadir_relocatable;
85 char *datadir_binreloc, *default_data_dir;
86
87 /* First, ask relocable-prog */
88 /* Put in a variable to avoid "comparison with string literal" */
89 default_data_dir = DEFAULT_DATA_DIR;
90 datadir_relocatable = relocate(default_data_dir);
91
92 /* Then ask binreloc - it handles ../share even if CWD != "bin"
93 (e.g. "src"). However it's not as precise and portable ($PATH
94 lookup, etc.). */
95 BrInitError error;
96 if (br_init(&error) == 0 && error != BR_INIT_ERROR_DISABLED)
97 {
98 log_warn("BinReloc failed to initialize (error code %d)", error);
99 datadir_binreloc = strdup(datadir_relocatable);
100 }
101 else
102 {
103 datadir_binreloc = br_find_data_dir(datadir_relocatable);
104 }
105
106 /* Free relocable-prog's path, if necessary */
107 if (datadir_relocatable != default_data_dir)
108 free((char *)datadir_relocatable);
109
110 /* BinReloc always return a newly allocated string, with either the
111 built directory, or a copy of its argument */
112 datadir = datadir_binreloc;
113 }
114
115 /** pkgdatadir (e.g. ".local/share/freedink") **/
116 {
117 pkgdatadir = br_build_path(datadir, PACKAGE);
118 }
119
120 /** exedir (e.g. "C:/Program Files/Dink Smallwood") **/
121 {
122 char* fullprogname = get_full_program_name();
123 if (fullprogname != NULL)
124 exefile = strdup(fullprogname);
125 else
126 exefile = strdup(argv0);
127 log_info("Hi, I'm '%s'", exefile);
128 /* gnulib's dir_name always returns a newly xalloc'd string */
129 exedir = dir_name(exefile);
130 }
131
132 /** refdir (e.g. "/usr/share/dink") **/
133 {
134 /** => refdir **/
135 char* match = NULL;
136 int nb_dirs = 8;
137 char** lookup = malloc(sizeof(char*) * nb_dirs);
138 int i = 0;
139 if (refdir_opt == NULL)
140 lookup[0] = NULL;
141 else
142 lookup[0] = refdir_opt;
143 lookup[1] = ".";
144 lookup[2] = exedir;
145
146 char *default3 = NULL, *default4 = NULL, *default5 = NULL,
147 *default6 = NULL, *default7 = NULL;
148 /* FHS mentions optional 'share/games' which some Debian packagers
149 seem to be found of */
150 /* Packagers: don't alter these paths. FreeDink must run in a
151 _consistent_ way across platforms. If you need an alternate
152 path, consider using ./configure --prefix=..., or contact
153 bug-freedink@gnu.org to discuss it. */
154 default3 = br_build_path(datadir, "dink");
155 default4 = "/usr/local/share/games/dink";
156 default5 = "/usr/local/share/dink";
157 default6 = "/usr/share/games/dink";
158 default7 = "/usr/share/dink";
159 lookup[3] = default3;
160 lookup[4] = default4;
161 lookup[5] = default5;
162 lookup[6] = default6;
163 lookup[7] = default7;
164
165 for (; i < nb_dirs; i++)
166 {
167 char *dir_graphics_ci = NULL, *dir_tiles_ci = NULL;
168 if (lookup[i] == NULL)
169 continue;
170 dir_graphics_ci = br_build_path(lookup[i], "dink/graphics");
171 dir_tiles_ci = br_build_path(lookup[i], "dink/tiles");
172 ciconvert(dir_graphics_ci);
173 ciconvert(dir_tiles_ci);
174 if (is_directory(dir_graphics_ci) && is_directory(dir_tiles_ci))
175 {
176 match = lookup[i];
177 }
178
179 if (match == NULL && i == 0)
180 {
181 msgbox_init_error("Invalid --refdir option: %s and/or %s are not accessible.",
182 dir_graphics_ci, dir_tiles_ci);
183 exit(1);
184 }
185
186 free(dir_graphics_ci);
187 free(dir_tiles_ci);
188 if (match != NULL)
189 break;
190 }
191 refdir = match;
192 if (refdir != NULL)
193 {
194 refdir = strdup(refdir);
195 }
196 else
197 {
198 char *msg = NULL;
199 asprintf_append(&msg, "Error: cannot find reference directory (--refdir). I looked in:\n");
200 // lookup[0] already treated above
201 asprintf_append(&msg, "- %s [current dir]\n", lookup[1]);
202 asprintf_append(&msg, "- %s [exedir]\n", lookup[2]);
203 asprintf_append(&msg, "- %s [detected prefix]\n", lookup[3]);
204 asprintf_append(&msg, "- %s [/usr/local/share/games prefix]\n", lookup[4]);
205 asprintf_append(&msg, "- %s [/usr/local/share prefix]\n", lookup[5]);
206 asprintf_append(&msg, "- %s [/usr/share/games prefix]\n", lookup[6]);
207 asprintf_append(&msg, "- %s [/usr/share prefix]\n", lookup[7]);
208 asprintf_append(&msg, "The reference directory contains among others the "
209 "'dink/graphics/' and 'dink/tiles/' directories (as well as "
210 "D-Mods).");
211 msgbox_init_error(msg);
212 free(msg);
213 exit(1);
214 }
215
216 free(default3); // br_build_path()
217 free(lookup);
218 }
219
220 /** fallbackdir (e.g. "/usr/share/dink/dink") **/
221 /* (directory used when a file cannot be found in a D-Mod) */
222 {
223 fallbackdir = br_strcat(refdir, "/dink");
224 }
225
226 /** dmoddir (e.g. "/usr/share/dink/island") **/
227 {
228 if (dmoddir_opt != NULL && is_directory(dmoddir_opt))
229 {
230 /* Use path given on the command line, either a full path or a
231 path relative to the current directory. */
232 /* Note: don't search for "dink" in the default dir if no
233 '-game' option was given */
234 dmoddir = strdup(dmoddir_opt);
235 }
236 else
237 {
238 /* Use path given on the command line, relative to $refdir */
239 char *subdir = dmoddir_opt;
240 if (subdir == NULL)
241 subdir = "dink";
242 dmoddir = malloc(strlen(refdir) + 1 + strlen(subdir) + 1);
243 strcpy(dmoddir, refdir);
244 strcat(dmoddir, "/");
245 strcat(dmoddir, subdir);
246 if (!is_directory(dmoddir))
247 {
248 char *msg = NULL;
249
250 asprintf_append(&msg, "Error: D-Mod directory '%s' doesn't exist. I looked in:\n", subdir);
251 if (dmoddir_opt != NULL)
252 asprintf_append(&msg, "- ./%s\n", dmoddir_opt);
253 asprintf_append(&msg, "- %s (refdir is '%s')", dmoddir, refdir);
254
255 msgbox_init_error(msg);
256 free(msg);
257 exit(1);
258 }
259 }
260 /* Strip slashes */
261 while (strlen(dmoddir) > 0 && dmoddir[strlen(dmoddir) - 1] == '/')
262 dmoddir[strlen(dmoddir) - 1] = '\0';
263 }
264
265 /** dmodname (e.g. "island") **/
266 /* Used to save games in ~/.dink/<dmod>/... */
267 {
268 dmodname = base_name(dmoddir);
269 if (strcmp(dmodname, ".") == 0)
270 {
271 free(dmodname);
272 char *canonical_dmoddir = canonicalize_file_name(dmoddir);
273 dmodname = base_name(canonical_dmoddir);
274 free(canonical_dmoddir);
275 }
276 }
277
278 /** userappdir (e.g. "~/.dink") **/
279 {
280 #if defined _WIN32 || defined __WIN32__ || defined __CYGWIN__
281 userappdir = malloc(MAX_PATH);
282 /* C:\Documents and Settings\name\Application Data */
283 SHGetSpecialFolderPath(NULL, userappdir, CSIDL_APPDATA, 1);
284 #else
285 char* envhome = getenv("HOME");
286 if (envhome != NULL)
287 userappdir = strdup(getenv("HOME"));
288 #endif
289 if (userappdir != NULL)
290 {
291 userappdir = realloc(userappdir, strlen(userappdir) + 1 + 1 + strlen(PACKAGE) + 1);
292 strcat(userappdir, "/");
293 #if defined _WIN32 || defined __WIN32__ || defined __CYGWIN__
294 #else
295 strcat(userappdir, ".");
296 #endif
297 strcat(userappdir, "dink");
298 }
299 else
300 {
301 // No detected home directory - saving in the reference
302 // directory
303 userappdir = strdup(refdir);
304 }
305 }
306
307 log_info("exedir = %s", exedir);
308 log_info("datadir = %s", datadir);
309 log_info("pkgdatadir = %s", pkgdatadir);
310 log_info("defaultpkgdatadir = %s", defaultpkgdatadir);
311 log_info("refdir = %s", refdir);
312 log_info("dmoddir = %s", dmoddir);
313 log_info("dmodname = %s", dmodname);
314 log_info("userappdir = %s", userappdir);
315
316 free(datadir);
317 free(refdir);
318 }
319
320
paths_getdefaultpkgdatadir(void)321 const char *paths_getdefaultpkgdatadir(void)
322 {
323 return defaultpkgdatadir;
324 }
325
326
paths_getpkgdatadir(void)327 const char *paths_getpkgdatadir(void)
328 {
329 return pkgdatadir;
330 }
paths_getdmoddir(void)331 const char *paths_getdmoddir(void)
332 {
333 return dmoddir;
334 }
paths_getdmodname(void)335 const char *paths_getdmodname(void)
336 {
337 return dmodname;
338 }
339
paths_getfallbackdir(void)340 const char *paths_getfallbackdir(void)
341 {
342 return fallbackdir;
343 }
344
paths_getexedir(void)345 const char *paths_getexedir(void)
346 {
347 return exedir;
348 }
349
paths_getexefile(void)350 const char *paths_getexefile(void)
351 {
352 return exefile;
353 }
354
355
paths_dmodfile(char * file)356 char* paths_dmodfile(char *file)
357 {
358 char *fullpath = br_build_path(dmoddir, file);
359 ciconvert(fullpath);
360 return fullpath;
361 }
362
paths_dmodfile_fopen(char * file,char * mode)363 FILE* paths_dmodfile_fopen(char *file, char *mode)
364 {
365 char *fullpath = paths_dmodfile(file);
366 FILE *result = fopen(fullpath, mode);
367 free(fullpath);
368 return result;
369 }
370
paths_fallbackfile(char * file)371 char* paths_fallbackfile(char *file)
372 {
373 char *fullpath = br_build_path(fallbackdir, file);
374 ciconvert(fullpath);
375 return fullpath;
376 }
377
paths_fallbackfile_fopen(char * file,char * mode)378 FILE* paths_fallbackfile_fopen(char *file, char *mode)
379 {
380 char *fullpath = paths_fallbackfile(file);
381 FILE *result = fopen(fullpath, mode);
382 free(fullpath);
383 return result;
384 }
385
paths_defaultpkgdatafile(char * file)386 char* paths_defaultpkgdatafile(char *file)
387 {
388 char *fullpath = br_build_path(defaultpkgdatadir, file);
389 ciconvert(fullpath);
390 return fullpath;
391 }
392
paths_defaultpkgdatafile_fopen(char * file,char * mode)393 FILE* paths_defaultpkgdatafile_fopen(char *file, char *mode)
394 {
395 char *fullpath = paths_defaultpkgdatafile(file);
396 FILE *result = fopen(fullpath, mode);
397 free(fullpath);
398 return result;
399 }
400
paths_pkgdatafile(char * file)401 char* paths_pkgdatafile(char *file)
402 {
403 char *fullpath = br_build_path(pkgdatadir, file);
404 ciconvert(fullpath);
405 return fullpath;
406 }
407
paths_pkgdatafile_fopen(char * file,char * mode)408 FILE* paths_pkgdatafile_fopen(char *file, char *mode)
409 {
410 char *fullpath = paths_pkgdatafile(file);
411 FILE *result = fopen(fullpath, mode);
412 free(fullpath);
413 return result;
414 }
415
paths_exedirfile(char * file)416 char* paths_exedirfile(char *file)
417 {
418 char *fullpath = br_build_path(exedir, file);
419 ciconvert(fullpath);
420 return fullpath;
421 }
422
paths_exedirfile_fopen(char * file,char * mode)423 FILE* paths_exedirfile_fopen(char *file, char *mode)
424 {
425 char *fullpath = paths_exedirfile(file);
426 FILE *result = fopen(fullpath, mode);
427 free(fullpath);
428 return result;
429 }
430
paths_savegame_fopen(int num,char * mode)431 FILE *paths_savegame_fopen(int num, char *mode)
432 {
433 char *fullpath_in_dmoddir = NULL;
434 char *fullpath_in_userappdir = NULL;
435 FILE *fp = NULL;
436
437 /* 20 decimal digits max for 64bit integer - should be enough :) */
438 char file[4 + 20 + 4 + 1];
439 sprintf(file, "save%d.dat", num);
440
441
442 /** fullpath_in_userappdir **/
443 char *savedir = strdup(userappdir);
444 savedir = realloc(savedir, strlen(userappdir) + 1 + strlen(dmodname) + 1);
445 strcat(savedir, "/");
446 strcat(savedir, dmodname);
447 /* Create directories if needed */
448 if (strchr(mode, 'w') != NULL || strchr(mode, 'a') != NULL)
449 /* Note: 0777 & umask => 0755 in common case */
450 if ((!is_directory(userappdir) && (mkdir(userappdir, 0777) < 0))
451 || (!is_directory(savedir) && (mkdir(savedir, 0777) < 0)))
452 {
453 free(savedir);
454 return NULL;
455 }
456 fullpath_in_userappdir = br_build_path(savedir, file);
457 ciconvert(fullpath_in_userappdir);
458 free(savedir);
459
460
461 /** fullpath_in_dmoddir **/
462 fullpath_in_dmoddir = paths_dmodfile(file);
463 ciconvert(fullpath_in_dmoddir);
464
465
466 /* Try ~/.dink (if present) when reading - but don't try that
467 first when writing */
468 if (strchr(mode, 'r') != NULL)
469 fp = fopen(fullpath_in_userappdir, mode);
470
471 /* Try in the D-Mod dir */
472 if (fp == NULL)
473 fp = fopen(fullpath_in_dmoddir, mode);
474
475 /* Then try in ~/.dink */
476 if (fp == NULL)
477 fp = fopen(fullpath_in_userappdir, mode);
478
479 free(fullpath_in_dmoddir);
480 free(fullpath_in_userappdir);
481
482 return fp;
483 }
484
paths_quit(void)485 void paths_quit(void)
486 {
487 free(defaultpkgdatadir);
488 free(pkgdatadir);
489 free(exedir);
490 free(fallbackdir);
491 free(dmoddir);
492 free(dmodname);
493 free(userappdir);
494 free(exefile);
495
496 defaultpkgdatadir = NULL;
497 pkgdatadir = NULL;
498 exedir = NULL;
499 fallbackdir = NULL;
500 dmoddir = NULL;
501 dmodname = NULL;
502 userappdir = NULL;
503 exefile = NULL;
504 }
505