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 }