26 |
28 |
27 struct Fio { |
29 struct Fio { |
28 byte *buffer, *buffer_end; ///< position pointer in local buffer and last valid byte of buffer |
30 byte *buffer, *buffer_end; ///< position pointer in local buffer and last valid byte of buffer |
29 uint32 pos; ///< current (system) position in file |
31 uint32 pos; ///< current (system) position in file |
30 FILE *cur_fh; ///< current file handle |
32 FILE *cur_fh; ///< current file handle |
|
33 const char *filename; ///< current filename |
31 FILE *handles[MAX_HANDLES]; ///< array of file handles we can have open |
34 FILE *handles[MAX_HANDLES]; ///< array of file handles we can have open |
32 byte buffer_start[FIO_BUFFER_SIZE]; ///< local buffer when read from file |
35 byte buffer_start[FIO_BUFFER_SIZE]; ///< local buffer when read from file |
|
36 const char *filenames[MAX_HANDLES]; ///< array of filenames we (should) have open |
33 #if defined(LIMITED_FDS) |
37 #if defined(LIMITED_FDS) |
34 uint open_handles; ///< current amount of open handles |
38 uint open_handles; ///< current amount of open handles |
35 const char *filename[MAX_HANDLES]; ///< array of filenames we (should) have open |
|
36 uint usage_count[MAX_HANDLES]; ///< count how many times this file has been opened |
39 uint usage_count[MAX_HANDLES]; ///< count how many times this file has been opened |
37 #endif /* LIMITED_FDS */ |
40 #endif /* LIMITED_FDS */ |
38 }; |
41 }; |
39 |
42 |
40 static Fio _fio; |
43 static Fio _fio; |
41 |
44 |
42 /* Get current position in file */ |
45 /* Get current position in file */ |
43 uint32 FioGetPos() |
46 uint32 FioGetPos() |
44 { |
47 { |
45 return _fio.pos + (_fio.buffer - _fio.buffer_start) - FIO_BUFFER_SIZE; |
48 return _fio.pos + (_fio.buffer - _fio.buffer_start) - FIO_BUFFER_SIZE; |
|
49 } |
|
50 |
|
51 const char *FioGetFilename() |
|
52 { |
|
53 return _fio.filename; |
46 } |
54 } |
47 |
55 |
48 void FioSeekTo(uint32 pos, int mode) |
56 void FioSeekTo(uint32 pos, int mode) |
49 { |
57 { |
50 if (mode == SEEK_CUR) pos += FioGetPos(); |
58 if (mode == SEEK_CUR) pos += FioGetPos(); |
172 f = FioFOpenFile(filename); |
181 f = FioFOpenFile(filename); |
173 if (f == NULL) error("Cannot open file '%s'", filename); |
182 if (f == NULL) error("Cannot open file '%s'", filename); |
174 |
183 |
175 FioCloseFile(slot); // if file was opened before, close it |
184 FioCloseFile(slot); // if file was opened before, close it |
176 _fio.handles[slot] = f; |
185 _fio.handles[slot] = f; |
177 #if defined(LIMITED_FDS) |
186 _fio.filenames[slot] = filename; |
178 _fio.filename[slot] = filename; |
187 #if defined(LIMITED_FDS) |
179 _fio.usage_count[slot] = 0; |
188 _fio.usage_count[slot] = 0; |
180 _fio.open_handles++; |
189 _fio.open_handles++; |
181 #endif /* LIMITED_FDS */ |
190 #endif /* LIMITED_FDS */ |
182 FioSeekToFile(slot << 24); |
191 FioSeekToFile(slot << 24); |
183 } |
192 } |
184 |
193 |
|
194 const char *_subdirs[NUM_SUBDIRS] = { |
|
195 "", |
|
196 "save" PATHSEP, |
|
197 "save" PATHSEP "autosave" PATHSEP, |
|
198 "scenario" PATHSEP, |
|
199 "scenario" PATHSEP "heightmap" PATHSEP, |
|
200 "gm" PATHSEP, |
|
201 "data" PATHSEP, |
|
202 "lang" PATHSEP |
|
203 }; |
|
204 |
|
205 const char *_searchpaths[NUM_SEARCHPATHS]; |
|
206 |
185 /** |
207 /** |
186 * Check whether the given file exists |
208 * Check whether the given file exists |
187 * @param filename the file to try for existance |
209 * @param filename the file to try for existance |
|
210 * @param subdir the subdirectory to look in |
188 * @return true if and only if the file can be opened |
211 * @return true if and only if the file can be opened |
189 */ |
212 */ |
190 bool FioCheckFileExists(const char *filename) |
213 bool FioCheckFileExists(const char *filename, Subdirectory subdir) |
191 { |
214 { |
192 FILE *f = FioFOpenFile(filename); |
215 FILE *f = FioFOpenFile(filename, "rb", subdir); |
193 if (f == NULL) return false; |
216 if (f == NULL) return false; |
194 |
217 |
195 fclose(f); |
218 fclose(f); |
196 return true; |
219 return true; |
197 } |
220 } |
198 |
221 |
199 /** |
222 char *FioGetFullPath(char *buf, size_t buflen, Searchpath sp, Subdirectory subdir, const char *filename) |
200 * Opens the file with the given name |
223 { |
201 * @param filename the file to open (in either data_dir or second_data_dir) |
224 assert(subdir < NUM_SUBDIRS); |
202 * @return the opened file or NULL when it failed. |
225 assert(sp < NUM_SEARCHPATHS); |
203 */ |
226 |
204 FILE *FioFOpenFile(const char *filename) |
227 snprintf(buf, buflen, "%s%s%s", _searchpaths[sp], _subdirs[subdir], filename); |
205 { |
228 return buf; |
206 FILE *f; |
229 } |
|
230 |
|
231 char *FioFindFullPath(char *buf, size_t buflen, Subdirectory subdir, const char *filename) |
|
232 { |
|
233 Searchpath sp; |
|
234 assert(subdir < NUM_SUBDIRS); |
|
235 |
|
236 FOR_ALL_SEARCHPATHS(sp) { |
|
237 FioGetFullPath(buf, buflen, sp, subdir, filename); |
|
238 if (FileExists(buf)) break; |
|
239 } |
|
240 |
|
241 return buf; |
|
242 } |
|
243 |
|
244 char *FioAppendDirectory(char *buf, size_t buflen, Searchpath sp, Subdirectory subdir) |
|
245 { |
|
246 assert(subdir < NUM_SUBDIRS); |
|
247 assert(sp < NUM_SEARCHPATHS); |
|
248 |
|
249 snprintf(buf, buflen, "%s%s", _searchpaths[sp], _subdirs[subdir]); |
|
250 return buf; |
|
251 } |
|
252 |
|
253 char *FioGetDirectory(char *buf, size_t buflen, Subdirectory subdir) |
|
254 { |
|
255 Searchpath sp; |
|
256 |
|
257 /* Find and return the first valid directory */ |
|
258 FOR_ALL_SEARCHPATHS(sp) { |
|
259 char *ret = FioAppendDirectory(buf, buflen, sp, subdir); |
|
260 if (FileExists(buf)) return ret; |
|
261 } |
|
262 |
|
263 /* Could not find the directory, fall back to a base path */ |
|
264 ttd_strlcpy(buf, _personal_dir, buflen); |
|
265 |
|
266 return buf; |
|
267 } |
|
268 |
|
269 FILE *FioFOpenFileSp(const char *filename, const char *mode, Searchpath sp, Subdirectory subdir) |
|
270 { |
|
271 #if defined(WIN32) && defined(UNICODE) |
|
272 /* fopen is implemented as a define with ellipses for |
|
273 * Unicode support (prepend an L). As we are not sending |
|
274 * a string, but a variable, it 'renames' the variable, |
|
275 * so make that variable to makes it compile happily */ |
|
276 wchar_t Lmode[5]; |
|
277 MultiByteToWideChar(CP_ACP, 0, mode, -1, Lmode, lengthof(Lmode)); |
|
278 #endif |
|
279 FILE *f = NULL; |
207 char buf[MAX_PATH]; |
280 char buf[MAX_PATH]; |
208 |
281 |
209 if (strrchr(filename, PATHSEPCHAR) == NULL) { |
282 if (subdir == NO_DIRECTORY) { |
210 snprintf(buf, lengthof(buf), "%s%s", _paths.data_dir, filename); |
283 ttd_strlcpy(buf, filename, lengthof(buf)); |
211 } else { |
284 } else { |
212 ttd_strlcpy(buf, filename, lengthof(buf)); |
285 snprintf(buf, lengthof(buf), "%s%s%s", _searchpaths[sp], _subdirs[subdir], filename); |
213 } |
286 } |
214 |
287 |
215 f = fopen(buf, "rb"); |
288 f = fopen(buf, mode); |
216 #if !defined(WIN32) |
289 #if !defined(WIN32) |
217 if (f == NULL) { |
290 if (f == NULL) { |
218 strtolower(strrchr(buf, PATHSEPCHAR)); |
291 strtolower(buf + strlen(_searchpaths[sp]) - 1); |
219 f = fopen(buf, "rb"); |
292 f = fopen(buf, mode); |
220 |
293 } |
221 #if defined SECOND_DATA_DIR |
294 #endif |
222 /* tries in the 2nd data directory */ |
295 return f; |
223 if (f == NULL) { |
296 } |
224 snprintf(buf, lengthof(buf), "%s%s", _paths.second_data_dir, filename); |
297 |
225 strtolower(buf + strlen(_paths.second_data_dir) - 1); |
298 /** Opens OpenTTD files somewhere in a personal or global directory */ |
226 f = fopen(buf, "rb"); |
299 FILE *FioFOpenFile(const char *filename, const char *mode, Subdirectory subdir) |
227 } |
300 { |
228 #endif |
301 FILE *f = NULL; |
229 } |
302 Searchpath sp; |
230 #endif |
303 |
|
304 assert(subdir < NUM_SUBDIRS || subdir == NO_DIRECTORY); |
|
305 |
|
306 FOR_ALL_SEARCHPATHS(sp) { |
|
307 f = FioFOpenFileSp(filename, mode, sp, subdir); |
|
308 if (f != NULL || subdir == NO_DIRECTORY) break; |
|
309 } |
231 |
310 |
232 return f; |
311 return f; |
233 } |
312 } |
234 |
313 |
235 /** |
314 /** |
302 * Determine the base (personal dir and game data dir) paths |
407 * Determine the base (personal dir and game data dir) paths |
303 * @param exe the path to the executable |
408 * @param exe the path to the executable |
304 */ |
409 */ |
305 void DetermineBasePaths(const char *exe) |
410 void DetermineBasePaths(const char *exe) |
306 { |
411 { |
307 /* Change the working directory to enable doubleclicking in UIs */ |
412 char tmp[MAX_PATH]; |
308 ChangeWorkingDirectory(exe); |
413 #if defined(__MORPHOS__) || defined(__AMIGA__) || !defined(WITH_PERSONAL_DIR) |
309 |
414 _searchpaths[SP_PERSONAL_DIR] = NULL; |
310 _paths.game_data_dir = MallocT<char>(MAX_PATH); |
|
311 ttd_strlcpy(_paths.game_data_dir, GAME_DATA_DIR, MAX_PATH); |
|
312 #if defined(SECOND_DATA_DIR) |
|
313 _paths.second_data_dir = MallocT<char>(MAX_PATH); |
|
314 ttd_strlcpy(_paths.second_data_dir, SECOND_DATA_DIR, MAX_PATH); |
|
315 #else |
415 #else |
316 _paths.second_data_dir = NULL; |
|
317 #endif |
|
318 |
|
319 #if defined(USE_HOMEDIR) |
|
320 const char *homedir = getenv("HOME"); |
416 const char *homedir = getenv("HOME"); |
321 |
417 |
322 if (homedir == NULL) { |
418 if (homedir == NULL) { |
323 const struct passwd *pw = getpwuid(getuid()); |
419 const struct passwd *pw = getpwuid(getuid()); |
324 if (pw != NULL) homedir = pw->pw_dir; |
420 homedir = (pw == NULL) ? "" : pw->pw_dir; |
325 } |
421 } |
326 |
422 |
327 _paths.personal_dir = str_fmt("%s" PATHSEP "%s", homedir, PERSONAL_DIR); |
423 snprintf(tmp, MAX_PATH, "%s" PATHSEP "%s", homedir, PERSONAL_DIR); |
328 #else /* not defined(USE_HOMEDIR) */ |
424 AppendPathSeparator(tmp, MAX_PATH); |
329 _paths.personal_dir = MallocT<char>(MAX_PATH); |
425 |
330 ttd_strlcpy(_paths.personal_dir, PERSONAL_DIR, MAX_PATH); |
426 _searchpaths[SP_PERSONAL_DIR] = strdup(tmp); |
331 |
427 #endif |
332 /* check if absolute or relative path */ |
428 _searchpaths[SP_SHARED_DIR] = NULL; |
333 const char *s = strchr(_paths.personal_dir, PATHSEPCHAR); |
429 |
334 |
430 #if defined(__MORPHOS__) || defined(__AMIGA__) |
335 /* add absolute path */ |
431 _searchpaths[SP_WORKING_DIR] = NULL; |
336 if (s == NULL || _paths.personal_dir != s) { |
432 #else |
337 getcwd(_paths.personal_dir, MAX_PATH); |
433 getcwd(tmp, MAX_PATH); |
338 AppendPathSeparator(_paths.personal_dir, MAX_PATH); |
434 AppendPathSeparator(tmp, MAX_PATH); |
339 ttd_strlcat(_paths.personal_dir, PERSONAL_DIR, MAX_PATH); |
435 _searchpaths[SP_WORKING_DIR] = strdup(tmp); |
340 } |
436 #endif |
341 #endif /* defined(USE_HOMEDIR) */ |
437 |
342 |
438 /* Change the working directory to that one of the executable */ |
343 AppendPathSeparator(_paths.personal_dir, MAX_PATH); |
439 ChangeWorkingDirectory((char*)exe); |
344 AppendPathSeparator(_paths.game_data_dir, MAX_PATH); |
440 getcwd(tmp, MAX_PATH); |
|
441 AppendPathSeparator(tmp, MAX_PATH); |
|
442 _searchpaths[SP_BINARY_DIR] = strdup(tmp); |
|
443 |
|
444 #if defined(__MORPHOS__) || defined(__AMIGA__) |
|
445 _searchpaths[SP_INSTALLATION_DIR] = NULL; |
|
446 #else |
|
447 snprintf(tmp, MAX_PATH, "%s", GLOBAL_DATA_DIR); |
|
448 AppendPathSeparator(tmp, MAX_PATH); |
|
449 _searchpaths[SP_INSTALLATION_DIR] = strdup(tmp); |
|
450 #endif |
|
451 #ifdef WITH_COCOA |
|
452 extern void cocoaSetApplicationBundleDir(); |
|
453 cocoaSetApplicationBundleDir(); |
|
454 #else |
|
455 _searchpaths[SP_APPLICATION_BUNDLE_DIR] = NULL; |
|
456 #endif |
345 } |
457 } |
346 #endif /* defined(WIN32) || defined(WINCE) */ |
458 #endif /* defined(WIN32) || defined(WINCE) */ |
|
459 |
|
460 char *_personal_dir; |
347 |
461 |
348 /** |
462 /** |
349 * Acquire the base paths (personal dir and game data dir), |
463 * Acquire the base paths (personal dir and game data dir), |
350 * fill all other paths (save dir, autosave dir etc) and |
464 * fill all other paths (save dir, autosave dir etc) and |
351 * make the save and scenario directories. |
465 * make the save and scenario directories. |
352 * @param exe the path to the executable |
466 * @param exe the path from the current path to the executable |
353 * @todo for save_dir, autosave_dir, scenario_dir and heightmap_dir the |
|
354 * assumption is that there is no path separator, however for gm_dir |
|
355 * lang_dir and data_dir that assumption is made. |
|
356 * This inconsistency should be resolved. |
|
357 */ |
467 */ |
358 void DeterminePaths(const char *exe) |
468 void DeterminePaths(const char *exe) |
359 { |
469 { |
360 DetermineBasePaths(exe); |
470 DetermineBasePaths(exe); |
361 |
471 |
362 _paths.save_dir = str_fmt("%ssave" PATHSEP, _paths.personal_dir); |
472 Searchpath sp; |
363 _paths.autosave_dir = str_fmt("%s" PATHSEP "autosave" PATHSEP, _paths.save_dir); |
473 FOR_ALL_SEARCHPATHS(sp) DEBUG(misc, 4, "%s added as search path", _searchpaths[sp]); |
364 _paths.scenario_dir = str_fmt("%sscenario" PATHSEP, _paths.personal_dir); |
474 |
365 _paths.heightmap_dir = str_fmt("%s" PATHSEP "heightmap" PATHSEP, _paths.scenario_dir); |
475 if (_config_file != NULL) { |
366 _paths.gm_dir = str_fmt("%sgm" PATHSEP, _paths.game_data_dir); |
476 _personal_dir = strdup(_config_file); |
367 _paths.data_dir = str_fmt("%sdata" PATHSEP, _paths.game_data_dir); |
477 char *end = strrchr(_personal_dir , PATHSEPCHAR); |
368 #if defined(CUSTOM_LANG_DIR) |
478 if (end == NULL) { |
369 /* Sets the search path for lng files to the custom one */ |
479 _personal_dir[0] = '\0'; |
370 _paths.lang_dir = MallocT<char>(MAX_PATH); |
480 } else { |
371 ttd_strlcpy(_paths.lang_dir, CUSTOM_LANG_DIR, MAX_PATH); |
481 end[1] = '\0'; |
372 AppendPathSeparator(_paths.lang_dir, MAX_PATH); |
482 } |
373 #else |
483 } else { |
374 _paths.lang_dir = str_fmt("%slang" PATHSEP, _paths.game_data_dir); |
484 char personal_dir[MAX_PATH]; |
375 #endif |
485 FioFindFullPath(personal_dir, lengthof(personal_dir), BASE_DIR, "openttd.cfg"); |
376 |
486 |
377 if (_config_file == NULL) { |
487 if (FileExists(personal_dir)) { |
378 _config_file = str_fmt("%sopenttd.cfg", _paths.personal_dir); |
488 char *end = strrchr(personal_dir, PATHSEPCHAR); |
379 } |
489 if (end != NULL) end[1] = '\0'; |
380 |
490 _personal_dir = strdup(personal_dir); |
381 _highscore_file = str_fmt("%shs.dat", _paths.personal_dir); |
491 _config_file = str_fmt("%sopenttd.cfg", _personal_dir); |
382 _log_file = str_fmt("%sopenttd.log", _paths.personal_dir); |
492 } else { |
383 |
493 static const Searchpath new_openttd_cfg_order[] = { |
384 /* Make (auto)save and scenario folder */ |
494 SP_PERSONAL_DIR, SP_BINARY_DIR, SP_WORKING_DIR, SP_SHARED_DIR, SP_INSTALLATION_DIR |
385 FioCreateDirectory(_paths.save_dir); |
495 }; |
386 FioCreateDirectory(_paths.autosave_dir); |
496 |
387 FioCreateDirectory(_paths.scenario_dir); |
497 for (uint i = 0; i < lengthof(new_openttd_cfg_order); i++) { |
388 FioCreateDirectory(_paths.heightmap_dir); |
498 if (IsValidSearchPath(new_openttd_cfg_order[i])) { |
389 } |
499 _personal_dir = strdup(_searchpaths[new_openttd_cfg_order[i]]); |
|
500 _config_file = str_fmt("%sopenttd.cfg", _personal_dir); |
|
501 break; |
|
502 } |
|
503 } |
|
504 } |
|
505 } |
|
506 |
|
507 DEBUG(misc, 3, "%s found as personal directory", _personal_dir); |
|
508 |
|
509 _highscore_file = str_fmt("%shs.dat", _personal_dir); |
|
510 _log_file = str_fmt("%sopenttd.log", _personal_dir); |
|
511 |
|
512 char *save_dir = str_fmt("%s%s", _personal_dir, FioGetSubdirectory(SAVE_DIR)); |
|
513 char *autosave_dir = str_fmt("%s%s", _personal_dir, FioGetSubdirectory(AUTOSAVE_DIR)); |
|
514 |
|
515 /* Make the necessary folders */ |
|
516 FioCreateDirectory(_personal_dir); |
|
517 FioCreateDirectory(save_dir); |
|
518 FioCreateDirectory(autosave_dir); |
|
519 |
|
520 free(save_dir); |
|
521 free(autosave_dir); |
|
522 } |
|
523 |
|
524 /** |
|
525 * Sanitizes a filename, i.e. removes all illegal characters from it. |
|
526 * @param filename the "\0" terminated filename |
|
527 */ |
|
528 void SanitizeFilename(char *filename) |
|
529 { |
|
530 for (; *filename != '\0'; filename++) { |
|
531 switch (*filename) { |
|
532 /* The following characters are not allowed in filenames |
|
533 * on at least one of the supported operating systems: */ |
|
534 case ':': case '\\': case '*': case '?': case '/': |
|
535 case '<': case '>': case '|': case '"': |
|
536 *filename = '_'; |
|
537 break; |
|
538 } |
|
539 } |
|
540 } |