Mercurial > molko
comparison src/libmlk-core/core/sys.c @ 320:8f9937403749
misc: improve loading of data
author | David Demelier <markand@malikania.fr> |
---|---|
date | Fri, 01 Oct 2021 20:30:00 +0200 |
parents | libmlk-core/core/sys.c@1a6125ffebff |
children | 7d7991f97acf |
comparison
equal
deleted
inserted
replaced
319:b843eef4cc35 | 320:8f9937403749 |
---|---|
1 /* | |
2 * sys.c -- system routines | |
3 * | |
4 * Copyright (c) 2020-2021 David Demelier <markand@malikania.fr> | |
5 * | |
6 * Permission to use, copy, modify, and/or distribute this software for any | |
7 * purpose with or without fee is hereby granted, provided that the above | |
8 * copyright notice and this permission notice appear in all copies. | |
9 * | |
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
17 */ | |
18 | |
19 #include "config.h" | |
20 | |
21 #include <sys/stat.h> | |
22 #include <assert.h> | |
23 #include <stdio.h> | |
24 #include <stdlib.h> | |
25 #include <limits.h> | |
26 | |
27 #if defined(_WIN32) | |
28 # include <shlwapi.h> | |
29 # include <windows.h> | |
30 #else | |
31 # include <sys/stat.h> | |
32 # include <errno.h> | |
33 # include <string.h> | |
34 #endif | |
35 | |
36 #include <SDL.h> | |
37 #include <SDL_image.h> | |
38 #include <SDL_mixer.h> | |
39 #include <SDL_ttf.h> | |
40 | |
41 #include <port/port.h> | |
42 | |
43 #include "error.h" | |
44 #include "sound.h" | |
45 #include "sys.h" | |
46 | |
47 static struct { | |
48 char organization[128]; | |
49 char name[128]; | |
50 } info = { | |
51 .organization = "fr.malikania", | |
52 .name = "molko" | |
53 }; | |
54 | |
55 static struct { | |
56 char bindir[PATH_MAX]; | |
57 char datadir[PATH_MAX]; | |
58 char localedir[PATH_MAX]; | |
59 } paths; | |
60 | |
61 static inline char * | |
62 normalize(char *str) | |
63 { | |
64 for (char *p = str; *p; ++p) | |
65 if (*p == '\\') | |
66 *p = '/'; | |
67 | |
68 return str; | |
69 } | |
70 | |
71 static inline int | |
72 absolute(const char *path) | |
73 { | |
74 #if defined(_WIN32) | |
75 return !PathIsRelativeA(path); | |
76 #else | |
77 /* Assuming UNIX like. */ | |
78 if (path[0] == '/') | |
79 return 1; | |
80 | |
81 return 0; | |
82 #endif | |
83 } | |
84 | |
85 static const char * | |
86 system_directory(const char *whichdir) | |
87 { | |
88 static char path[PATH_MAX]; | |
89 static char ret[PATH_MAX]; | |
90 char *base, *binsect; | |
91 | |
92 /* | |
93 * Some system does not provide support (shame on you OpenBSD) | |
94 * to the executable path. In that case we use PREFIX+<dir> | |
95 * instead unless <dir> is already absolute. | |
96 */ | |
97 | |
98 /* Component (e.g. MLK_BINDIR is set to /bin), return immediately. */ | |
99 if (absolute(whichdir)) | |
100 return whichdir; | |
101 | |
102 /* Determine base executable path. */ | |
103 if (!(base = SDL_GetBasePath())) { | |
104 if (absolute(whichdir)) | |
105 strlcpy(ret, whichdir, sizeof (ret)); | |
106 else | |
107 snprintf(ret, sizeof (ret), "%s/%s", MLK_PREFIX, whichdir); | |
108 } else { | |
109 /* | |
110 * Decompose the path to the given special directory by | |
111 * computing relative directory to it from where the | |
112 * binary is located. | |
113 * | |
114 * Example: | |
115 * | |
116 * PREFIX/bin/mlk | |
117 * PREFIX/share/mlk-adventure | |
118 * | |
119 * The path to the data is ../share/molko starting from | |
120 * the binary. | |
121 * | |
122 * Put the base path into the path and remove the value | |
123 * of MLK_BINDIR. | |
124 * | |
125 * Example: | |
126 * from: /usr/local/bin | |
127 * to: /usr/local | |
128 */ | |
129 strlcpy(path, base, sizeof (path)); | |
130 SDL_free(base); | |
131 | |
132 if ((binsect = strstr(path, MLK_BINDIR))) | |
133 *binsect = '\0'; | |
134 | |
135 snprintf(ret, sizeof (ret), "%s%s", path, whichdir); | |
136 } | |
137 | |
138 return normalize(ret); | |
139 } | |
140 | |
141 static const char * | |
142 user_directory(enum sys_dir kind) | |
143 { | |
144 /* Kept for future use. */ | |
145 (void)kind; | |
146 | |
147 static char path[PATH_MAX]; | |
148 char *pref; | |
149 | |
150 if ((pref = SDL_GetPrefPath(info.organization, info.name))) { | |
151 strlcpy(path, pref, sizeof (path)); | |
152 SDL_free(pref); | |
153 } else | |
154 strlcpy(path, "./", sizeof (path)); | |
155 | |
156 return path; | |
157 } | |
158 | |
159 static inline int | |
160 mkpath(const char *path) | |
161 { | |
162 #ifdef _WIN32 | |
163 /* TODO: add error using the convenient FormatMessage function. */ | |
164 if (!CreateDirectoryA(path, NULL) && GetLastError() != ERROR_ALREADY_EXISTS) | |
165 return errorf("unable to create directory: %s", path); | |
166 #else | |
167 if (mkdir(path, 0755) < 0 && errno != EEXIST) | |
168 return errorf("%s", strerror(errno)); | |
169 #endif | |
170 | |
171 return 0; | |
172 } | |
173 | |
174 static inline void | |
175 set_bindir(void) | |
176 { | |
177 strlcpy(paths.bindir, system_directory(MLK_BINDIR), sizeof (paths.bindir)); | |
178 } | |
179 | |
180 static void | |
181 set_datadir(void) | |
182 { | |
183 char *base, test[PATH_MAX]; | |
184 struct stat st; | |
185 | |
186 /* | |
187 * For convenient purposes, if we're running executables directly from | |
188 * source tree, check for libmlk-data presence and use it. Otherwise | |
189 * use standard system directory with the project named concatenated. | |
190 */ | |
191 if ((base = SDL_GetBasePath())) { | |
192 snprintf(test, sizeof (test), "%ssrc/libmlk-data", base); | |
193 | |
194 if (stat(test, &st) == 0 && S_ISDIR(st.st_mode)) { | |
195 strlcpy(paths.datadir, test, sizeof (paths.datadir)); | |
196 normalize(paths.datadir); | |
197 } | |
198 | |
199 free(base); | |
200 } | |
201 | |
202 /* Not found, use standard. */ | |
203 if (!paths.datadir[0]) | |
204 snprintf(paths.datadir, sizeof (paths.datadir), "%s/%s", | |
205 system_directory(MLK_DATADIR), info.name); | |
206 } | |
207 | |
208 static inline void | |
209 set_localedir(void) | |
210 { | |
211 strlcpy(paths.localedir, system_directory(MLK_BINDIR), sizeof (paths.localedir)); | |
212 } | |
213 | |
214 int | |
215 sys_init(const char *organization, const char *name) | |
216 { | |
217 #if defined(__MINGW64__) | |
218 /* On MinGW buffering leads to painful debugging. */ | |
219 setbuf(stderr, NULL); | |
220 setbuf(stdout, NULL); | |
221 #endif | |
222 set_bindir(); | |
223 set_datadir(); | |
224 set_localedir(); | |
225 | |
226 strlcpy(info.organization, organization, sizeof (info.organization)); | |
227 strlcpy(info.name, name, sizeof (info.name)); | |
228 | |
229 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) | |
230 return errorf("%s", SDL_GetError()); | |
231 if (IMG_Init(IMG_INIT_PNG) != IMG_INIT_PNG) | |
232 return errorf("%s", SDL_GetError()); | |
233 if (TTF_Init() < 0) | |
234 return errorf("%s", SDL_GetError()); | |
235 if (Mix_Init(MIX_INIT_OGG) != MIX_INIT_OGG) | |
236 return errorf("%s", SDL_GetError()); | |
237 if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 4096) < 0) | |
238 return errorf("%s", SDL_GetError()); | |
239 | |
240 Mix_AllocateChannels(SOUND_CHANNELS_MAX); | |
241 | |
242 return 0; | |
243 } | |
244 | |
245 const char * | |
246 sys_dir(enum sys_dir kind) | |
247 { | |
248 switch (kind) { | |
249 case SYS_DIR_BIN: | |
250 return paths.bindir; | |
251 case SYS_DIR_DATA: | |
252 return paths.datadir; | |
253 case SYS_DIR_LOCALE: | |
254 return paths.localedir; | |
255 default: | |
256 return user_directory(kind); | |
257 } | |
258 } | |
259 | |
260 int | |
261 sys_mkdir(const char *directory) | |
262 { | |
263 char path[PATH_MAX], *p; | |
264 | |
265 /* Copy the directory to normalize and iterate over '/'. */ | |
266 strlcpy(path, directory, sizeof (path)); | |
267 normalize(path); | |
268 | |
269 #if defined(_WIN32) | |
270 /* Remove drive letter that we don't need. */ | |
271 if ((p = strchr(path, ':'))) | |
272 ++p; | |
273 else | |
274 p = path; | |
275 #else | |
276 p = path; | |
277 #endif | |
278 | |
279 for (p = p + 1; *p; ++p) { | |
280 if (*p == '/') { | |
281 *p = 0; | |
282 | |
283 if (mkpath(path) < 0) | |
284 return -1; | |
285 | |
286 *p = '/'; | |
287 } | |
288 } | |
289 | |
290 return mkpath(path); | |
291 } | |
292 | |
293 void | |
294 sys_finish(void) | |
295 { | |
296 Mix_Quit(); | |
297 TTF_Quit(); | |
298 IMG_Quit(); | |
299 SDL_Quit(); | |
300 } |