PageRenderTime 49ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/filesystem.c

https://gitlab.com/fzwoch/fodquake
C | 889 lines | 742 code | 99 blank | 48 comment | 78 complexity | 4a6bb7839fe519b5fb2733f5056cbdd4 MD5 | raw file
Possible License(s): GPL-2.0
  1. /*
  2. Copyright (C) 1996-1997 Id Software, Inc.
  3. Copyright (C) 2007 Mark Olsen
  4. This program is free software; you can redistribute it and/or
  5. modify it under the terms of the GNU General Public License
  6. as published by the Free Software Foundation; either version 2
  7. of the License, or (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  11. See the GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program; if not, write to the Free Software
  14. Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  15. */
  16. #include <stdlib.h>
  17. #include <string.h>
  18. #include "common.h"
  19. #include "sys_io.h"
  20. #include "filesystem.h"
  21. #include "draw.h"
  22. #include "strl.h"
  23. #include "context_sensitive_tab.h"
  24. #include "utils.h"
  25. #include "tokenize_string.h"
  26. // in memory
  27. struct packfile
  28. {
  29. char name[MAX_QPATH];
  30. int filepos, filelen;
  31. } packfile_t;
  32. struct pack
  33. {
  34. char filename[MAX_OSPATH];
  35. FILE *handle;
  36. int numfiles;
  37. struct packfile *files;
  38. };
  39. struct dpackheader
  40. {
  41. char id[4];
  42. int dirofs;
  43. int dirlen;
  44. };
  45. struct dpackfile
  46. {
  47. char name[56];
  48. int filepos, filelen;
  49. };
  50. // on disk
  51. struct searchpath
  52. {
  53. char filename[MAX_OSPATH];
  54. struct pack *pack; // only one of filename / pack will be used
  55. struct searchpath *next;
  56. };
  57. static struct searchpath *com_searchpaths;
  58. static struct searchpath *com_base_searchpaths; // without gamedirs
  59. static const char * const skins_endings[] = { ".pcx", ".png", NULL}; //endings for skins files
  60. static int FS_FileLength(FILE * f)
  61. {
  62. int pos, end;
  63. pos = ftell(f);
  64. fseek(f, 0, SEEK_END);
  65. end = ftell(f);
  66. fseek(f, pos, SEEK_SET);
  67. return end;
  68. }
  69. int FS_FileOpenRead(const char *path, FILE ** hndl)
  70. {
  71. FILE *f;
  72. if (!(f = fopen(path, "rb")))
  73. {
  74. *hndl = NULL;
  75. return -1;
  76. }
  77. *hndl = f;
  78. return FS_FileLength(f);
  79. }
  80. //The filename will be prefixed by com_basedir
  81. qboolean FS_WriteFile(const char *filename, void *data, int len)
  82. {
  83. FILE *f;
  84. char name[MAX_OSPATH];
  85. snprintf(name, sizeof(name), "%s/%s", com_basedir, filename);
  86. if (!(f = fopen(name, "wb")))
  87. {
  88. FS_CreatePath(name);
  89. if (!(f = fopen(name, "wb")))
  90. return false;
  91. }
  92. Sys_Printf("FS_WriteFile: %s\n", name);
  93. fwrite(data, 1, len, f);
  94. fclose(f);
  95. return true;
  96. }
  97. //Only used for CopyFile and download
  98. void FS_CreatePath(char *path)
  99. {
  100. char *s, save;
  101. if (!*path)
  102. return;
  103. for (s = path + 1; *s; s++)
  104. {
  105. #ifdef _WIN32
  106. if (*s == '/' || *s == '\\')
  107. {
  108. #else
  109. if (*s == '/')
  110. {
  111. #endif
  112. save = *s;
  113. *s = 0;
  114. Sys_IO_Create_Directory(path);
  115. *s = save;
  116. }
  117. }
  118. }
  119. //Finds the file in the search path.
  120. //Sets com_filesize, com_netpath and one of handle or file
  121. int file_from_pak; // global indicating file came from pack file ZOID
  122. int file_from_gamedir;
  123. static int pakfile_compare(const void *a, const void *b)
  124. {
  125. const struct packfile *paka, *pakb;
  126. paka = a;
  127. pakb = b;
  128. return strcmp(paka->name, pakb->name);
  129. }
  130. int FS_FOpenFile(const char *filename, FILE ** file)
  131. {
  132. struct searchpath *search;
  133. struct pack *pak;
  134. struct packfile *pakfile;
  135. *file = NULL;
  136. file_from_pak = 0;
  137. file_from_gamedir = 1;
  138. com_filesize = -1;
  139. com_netpath[0] = 0;
  140. // search through the path, one element at a time
  141. for (search = com_searchpaths; search; search = search->next)
  142. {
  143. if (search == com_base_searchpaths && com_searchpaths != com_base_searchpaths)
  144. file_from_gamedir = 0;
  145. // is the element a pak file?
  146. if (search->pack)
  147. {
  148. // look through all the pak file elements
  149. pak = search->pack;
  150. pakfile = bsearch(filename, pak->files, pak->numfiles, sizeof(*pak->files), pakfile_compare);
  151. if (pakfile)
  152. {
  153. if (developer.value)
  154. Sys_Printf("PackFile: %s : %s\n", pak->filename, filename);
  155. // open a new file on the pakfile
  156. if (!(*file = fopen(pak->filename, "rb")))
  157. Sys_Error("Couldn't reopen %s", pak->filename);
  158. fseek(*file, pakfile->filepos, SEEK_SET);
  159. com_filesize = pakfile->filelen;
  160. file_from_pak = 1;
  161. snprintf(com_netpath, sizeof(com_netpath), "%s#%i", pak->filename, pakfile - pak->files);
  162. return com_filesize;
  163. }
  164. }
  165. else
  166. {
  167. snprintf(com_netpath, sizeof(com_netpath), "%s/%s", search->filename, filename);
  168. if (!(*file = fopen(com_netpath, "rb")))
  169. continue;
  170. if (developer.value)
  171. Sys_Printf("FindFile: %s\n", com_netpath);
  172. com_filesize = FS_FileLength(*file);
  173. return com_filesize;
  174. }
  175. }
  176. if (developer.value)
  177. Sys_Printf("FindFile: can't find %s\n", filename);
  178. return -1;
  179. }
  180. int FS_FileExists(const char *filename)
  181. {
  182. struct searchpath *search;
  183. struct pack *pak;
  184. struct packfile *pakfile;
  185. FILE *f;
  186. // search through the path, one element at a time
  187. for (search = com_searchpaths; search; search = search->next)
  188. {
  189. // is the element a pak file?
  190. if (search->pack)
  191. {
  192. // look through all the pak file elements
  193. pak = search->pack;
  194. pakfile = bsearch(filename, pak->files, pak->numfiles, sizeof(*pak->files), pakfile_compare);
  195. if (pakfile)
  196. {
  197. return 1;
  198. }
  199. }
  200. else
  201. {
  202. snprintf(com_netpath, sizeof(com_netpath), "%s/%s", search->filename, filename);
  203. if (!(f = fopen(com_netpath, "rb")))
  204. continue;
  205. fclose(f);
  206. return 1;
  207. }
  208. }
  209. return 0;
  210. }
  211. //Filename are relative to the quake directory.
  212. //Always appends a 0 byte to the loaded data.
  213. static byte *FS_LoadFile(const char *path, int usehunk)
  214. {
  215. FILE *h;
  216. byte *buf;
  217. int len;
  218. int r;
  219. buf = NULL; // quiet compiler warning
  220. // look for it in the filesystem or pack files
  221. len = com_filesize = FS_FOpenFile(path, &h);
  222. if (!h)
  223. return NULL;
  224. if (usehunk == 0)
  225. {
  226. buf = Z_Malloc(len + 1);
  227. }
  228. else if (usehunk == 5)
  229. {
  230. buf = malloc(len + 1);
  231. }
  232. else
  233. {
  234. Sys_Error("FS_LoadFile: bad usehunk");
  235. }
  236. if (!buf)
  237. Sys_Error("FS_LoadFile: not enough space for %s", path);
  238. ((byte *) buf)[len] = 0;
  239. r = fread(buf, 1, len, h);
  240. fclose(h);
  241. if (r != len)
  242. Sys_Error("FS_LoadFile: Error while reading file %s", path);
  243. return buf;
  244. }
  245. void *FS_LoadZFile(const char *path)
  246. {
  247. return FS_LoadFile(path, 0);
  248. }
  249. void *FS_LoadMallocFile(const char *path)
  250. {
  251. return FS_LoadFile(path, 5);
  252. }
  253. static int packfile_name_compare(const void *pack1, const void *pack2)
  254. {
  255. return strcmp(((const struct packfile *)pack1)->name, ((const struct packfile *)pack2)->name);
  256. }
  257. /*
  258. Takes an explicit (not game tree related) path to a pak file.
  259. Loads the header and directory, adding the files at the beginning
  260. of the list so they override previous pack files.
  261. */
  262. static struct pack *FS_LoadPackFile(char *packfile)
  263. {
  264. struct dpackheader header;
  265. int i;
  266. int j;
  267. int filelen;
  268. struct packfile *newfiles;
  269. struct pack *pack;
  270. FILE *packhandle;
  271. struct dpackfile *info;
  272. if ((filelen = FS_FileOpenRead(packfile, &packhandle)) == -1)
  273. return NULL;
  274. fread(&header, 1, sizeof(header), packhandle);
  275. if (filelen < 12 || header.id[0] != 'P' || header.id[1] != 'A' || header.id[2] != 'C' || header.id[3] != 'K')
  276. Sys_Error("%s is not a packfile", packfile);
  277. header.dirofs = LittleLong(header.dirofs);
  278. header.dirlen = LittleLong(header.dirlen);
  279. if (header.dirofs < 0 || header.dirlen < 0 || header.dirofs > filelen || header.dirofs + header.dirlen > filelen || header.dirofs + header.dirlen < header.dirofs)
  280. Sys_Error("%s is a malformed packfile", packfile);
  281. pack = Q_Malloc(sizeof(*pack));
  282. strcpy(pack->filename, packfile);
  283. pack->handle = packhandle;
  284. pack->numfiles = header.dirlen / sizeof(struct dpackfile);
  285. pack->files = newfiles = Q_Malloc(pack->numfiles * sizeof(struct packfile));
  286. info = Q_Malloc(header.dirlen);
  287. fseek(packhandle, header.dirofs, SEEK_SET);
  288. fread(info, 1, header.dirlen, packhandle);
  289. // parse the directory
  290. for (i = 0; i < pack->numfiles; i++)
  291. {
  292. for(j=0;info[i].name[j]&&j<sizeof(info[i].name);j++);
  293. if (j == sizeof(info[i].name))
  294. Sys_Error("%s is a malformed packfile", packfile);
  295. strcpy(newfiles[i].name, info[i].name);
  296. newfiles[i].filepos = LittleLong(info[i].filepos);
  297. newfiles[i].filelen = LittleLong(info[i].filelen);
  298. if (newfiles[i].filepos < 0 || newfiles[i].filelen < 0 || newfiles[i].filepos > filelen || newfiles[i].filepos + newfiles[i].filelen > filelen || newfiles[i].filepos + newfiles[i].filelen < newfiles[i].filepos)
  299. Sys_Error("%s is a malformed packfile", packfile);
  300. }
  301. free(info);
  302. /* Sort the entries by name to make it easier to search */
  303. qsort(newfiles, pack->numfiles, sizeof(*newfiles), packfile_name_compare);
  304. return pack;
  305. }
  306. static void FS_FreePackFile(struct pack *pack)
  307. {
  308. fclose(pack->handle);
  309. free(pack->files);
  310. free(pack);
  311. }
  312. static void FS_FreeSearchPaths(struct searchpath *searchpaths)
  313. {
  314. struct searchpath *t;
  315. while((t = searchpaths))
  316. {
  317. searchpaths = searchpaths->next;
  318. if (t->pack)
  319. FS_FreePackFile(t->pack);
  320. free(t);
  321. }
  322. }
  323. static const char *fs_basedirs[5];
  324. //Sets com_gamedir, adds the directory to the head of the path, then loads and adds pak1.pak pak2.pak ...
  325. static void FS_AddGameDirectory_NoReally(const char *dir)
  326. {
  327. int i;
  328. struct searchpath *firstsearch;
  329. struct searchpath *search;
  330. struct pack *pak;
  331. char pakfile[MAX_OSPATH], *p;
  332. int error;
  333. if (strlen(dir) + 1 >= sizeof(com_gamedir))
  334. {
  335. Sys_Error("Game directory path too long\n");
  336. return;
  337. }
  338. p = strrchr(dir, '/');
  339. if (p == 0)
  340. return;
  341. if (strlen(p) + 1 >= sizeof(com_gamedirfile))
  342. {
  343. Sys_Error("Game directory path too long\n");
  344. return;
  345. }
  346. strcpy(com_gamedirfile, p + 1);
  347. strcpy(com_gamedir, dir);
  348. search = com_searchpaths;
  349. while(search)
  350. {
  351. if (strcmp(search->filename, dir) == 0)
  352. break;
  353. search = search->next;
  354. }
  355. /* Path already exists */
  356. if (search)
  357. return;
  358. // add the directory to the search path
  359. search = malloc(sizeof(*search));
  360. if (search)
  361. {
  362. strcpy(search->filename, dir);
  363. search->pack = NULL;
  364. search->next = com_searchpaths;
  365. firstsearch = search;
  366. error = 0;
  367. // add any pak files in the format pak0.pak pak1.pak, ...
  368. for (i = 0;; i++)
  369. {
  370. search = malloc(sizeof(*search));
  371. if (search == 0)
  372. {
  373. error = 1;
  374. break;
  375. }
  376. snprintf(pakfile, sizeof(pakfile), "%s/pak%i.pak", dir, i);
  377. if (!(pak = FS_LoadPackFile(pakfile)))
  378. {
  379. free(search);
  380. break;
  381. }
  382. strlcpy(search->filename, pakfile, sizeof(search->filename));
  383. search->pack = pak;
  384. search->next = firstsearch;
  385. firstsearch = search;
  386. }
  387. if (!error)
  388. {
  389. com_searchpaths = firstsearch;
  390. return;
  391. }
  392. FS_FreeSearchPaths(firstsearch);
  393. com_searchpaths = 0;
  394. }
  395. Sys_Error("FS_AddGameDirectory: Failed to add \"%s\"\n", dir);
  396. }
  397. static void FS_AddGameDirectory(const char *dir)
  398. {
  399. unsigned int i;
  400. i = 0;
  401. while(fs_basedirs[i])
  402. {
  403. FS_AddGameDirectory_NoReally(va("%s/%s", fs_basedirs[i], dir));
  404. i++;
  405. }
  406. }
  407. //Sets the gamedir and path to a different directory.
  408. void FS_SetGamedir(const char *dir)
  409. {
  410. struct searchpath *search;
  411. if (strstr(dir, "..") || strstr(dir, "/") || strstr(dir, "\\") || strstr(dir, ":"))
  412. {
  413. Com_Printf("Gamedir should be a single filename, not a path\n");
  414. return;
  415. }
  416. if (!strcmp(com_gamedirfile, dir))
  417. return; // still the same
  418. // free up any current game dir info
  419. if (com_searchpaths != com_base_searchpaths)
  420. {
  421. search = com_searchpaths;
  422. while(search->next != com_base_searchpaths)
  423. search = search->next;
  424. search->next = 0;
  425. FS_FreeSearchPaths(com_searchpaths);
  426. com_searchpaths = com_base_searchpaths;
  427. }
  428. FS_AddGameDirectory(dir);
  429. }
  430. const char *ro_data_path;
  431. const char *user_data_path;
  432. const char *legacy_data_path;
  433. static int FS_CheckSubpathExist(const char *path, const char *subpath)
  434. {
  435. char *p;
  436. int ret;
  437. p = malloc(strlen(path) + 1 + strlen(subpath) + 1);
  438. if (p == 0)
  439. Sys_Error("Cry, cry");
  440. sprintf(p, "%s/%s", path, subpath);
  441. ret = Sys_IO_Path_Exists(p);
  442. free(p);
  443. return ret;
  444. }
  445. void FS_InitFilesystem(void)
  446. {
  447. int i;
  448. int preferlegacy;
  449. int legacywritable;
  450. int userwritable;
  451. unsigned int dircount;
  452. ro_data_path = Sys_GetRODataPath();
  453. user_data_path = Sys_GetUserDataPath();
  454. legacy_data_path = Sys_GetLegacyDataPath();
  455. preferlegacy = 0;
  456. legacywritable = 0;
  457. if (legacy_data_path)
  458. {
  459. if (Sys_IO_Path_Exists(legacy_data_path) && (FS_CheckSubpathExist(legacy_data_path, "id1") || FS_CheckSubpathExist(legacy_data_path, "fodquake") || FS_CheckSubpathExist(legacy_data_path, "qw")))
  460. {
  461. legacywritable = Sys_IO_Path_Writable(legacy_data_path);
  462. if (legacywritable)
  463. {
  464. preferlegacy = FS_CheckSubpathExist(legacy_data_path, "fodquake/temp"); /* That directory is created by MT_Init() */
  465. }
  466. }
  467. else
  468. {
  469. Sys_FreePathString(legacy_data_path);
  470. legacy_data_path = 0;
  471. }
  472. }
  473. userwritable = 0;
  474. if (user_data_path)
  475. {
  476. Sys_IO_Create_Directory(user_data_path);
  477. if (Sys_IO_Path_Exists(user_data_path))
  478. {
  479. userwritable = Sys_IO_Path_Writable(user_data_path);
  480. }
  481. else
  482. {
  483. Sys_FreePathString(user_data_path);
  484. user_data_path = 0;
  485. }
  486. }
  487. if (!legacywritable && !userwritable)
  488. {
  489. Sys_Error("Nowhere to write data. Check that either \"%s\" or \"%s\" is writable.\n", legacy_data_path?legacy_data_path:"<none>", user_data_path?user_data_path:"<none>");
  490. }
  491. dircount = 0;
  492. if (ro_data_path)
  493. {
  494. fs_basedirs[dircount++] = ro_data_path;
  495. }
  496. if (user_data_path && preferlegacy)
  497. {
  498. fs_basedirs[dircount++] = user_data_path;
  499. }
  500. if (legacy_data_path)
  501. {
  502. fs_basedirs[dircount++] = legacy_data_path;
  503. }
  504. if (user_data_path && !preferlegacy)
  505. {
  506. fs_basedirs[dircount++] = user_data_path;
  507. }
  508. if ((i = COM_CheckParm("-basedir")) && i < com_argc - 1)
  509. {
  510. #warning Create a Sys_ function for converting paths.
  511. for (i = 0; i < strlen(com_basedir); i++)
  512. if (com_basedir[i] == '\\')
  513. com_basedir[i] = '/';
  514. fs_basedirs[dircount++] = user_data_path;
  515. }
  516. strlcpy(com_basedir, fs_basedirs[dircount - 1], sizeof(com_basedir));
  517. fs_basedirs[dircount] = 0;
  518. FS_AddGameDirectory("id1");
  519. FS_AddGameDirectory("fodquake");
  520. FS_AddGameDirectory("qw");
  521. i = strlen(com_basedir) - 1;
  522. if (i >= 0 && com_basedir[i] == '/')
  523. com_basedir[i] = 0;
  524. // any set gamedirs will be freed up to here
  525. com_base_searchpaths = com_searchpaths;
  526. // the user might want to override default game directory
  527. if (!(i = COM_CheckParm("-game")))
  528. i = COM_CheckParm("+gamedir");
  529. if (i && i < com_argc - 1)
  530. FS_SetGamedir(com_argv[i + 1]);
  531. }
  532. void FS_ShutdownFilesystem(void)
  533. {
  534. FS_FreeSearchPaths(com_searchpaths);
  535. com_searchpaths = 0;
  536. if (ro_data_path)
  537. Sys_FreePathString(ro_data_path);
  538. if (user_data_path)
  539. Sys_FreePathString(user_data_path);
  540. if (legacy_data_path)
  541. Sys_FreePathString(legacy_data_path);
  542. }
  543. static void FS_Path_f(void)
  544. {
  545. struct searchpath *s;
  546. Com_Printf("Current search path:\n");
  547. for (s = com_searchpaths; s; s = s->next)
  548. {
  549. if (s == com_base_searchpaths)
  550. Com_Printf("----------\n");
  551. if (s->pack)
  552. Com_Printf("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
  553. else
  554. Com_Printf("%s\n", s->filename);
  555. }
  556. }
  557. struct cstc_skindata
  558. {
  559. qboolean initialized;
  560. qboolean *checked;
  561. struct directory_list *dl;
  562. struct Picture *picture;
  563. };
  564. static int cstc_skins_get_data(struct cst_info *self, int remove)
  565. {
  566. struct cstc_skindata *data;
  567. if (!self)
  568. return 1;
  569. if (self->data)
  570. {
  571. data = (struct cstc_skindata *)self->data;
  572. Util_Dir_Delete(data->dl);
  573. if (data->picture)
  574. Draw_FreePicture(data->picture);
  575. free(data);
  576. self->data = NULL;
  577. }
  578. if (remove)
  579. return 0;
  580. if ((data = calloc(1, sizeof(*data))))
  581. {
  582. if ((data->dl = Util_Dir_Read(va("%s/qw/skins/", com_basedir), 1, 1, skins_endings)))
  583. {
  584. if (data->dl->entry_count != 0)
  585. {
  586. self->data = (void *)data;
  587. return 0;
  588. }
  589. else
  590. {
  591. Com_Printf(va("%s/qw/skins/ has no skin files in it.\n", com_basedir));
  592. }
  593. Util_Dir_Delete(data->dl);
  594. }
  595. free(data);
  596. }
  597. return 1;
  598. }
  599. static int cstc_skins_check(char *entry, struct tokenized_string *ts)
  600. {
  601. int i;
  602. for (i=0; i<ts->count; i++)
  603. {
  604. if (Util_strcasestr(entry, ts->tokens[i]) == NULL)
  605. return 0;
  606. }
  607. return 1;
  608. }
  609. static int cstc_skins_get_results(struct cst_info *self, int *results, int get_result, int result_type, char **result)
  610. {
  611. struct cstc_skindata *data;
  612. int count, i;
  613. if (self->data == NULL)
  614. return 1;
  615. data = (struct cstc_skindata *)self->data;
  616. if (results || data->initialized == false)
  617. {
  618. if (data->checked)
  619. free(data->checked);
  620. if ((data->checked = calloc(data->dl->entry_count, sizeof(qboolean))) == NULL)
  621. return 1;
  622. for (i=0, count=0; i<data->dl->entry_count; i++)
  623. {
  624. if (cstc_skins_check(data->dl->entries[i].name, self->tokenized_input))
  625. {
  626. data->checked[i] = true;
  627. count++;
  628. }
  629. }
  630. if (results)
  631. *results = count;
  632. data->initialized = true;
  633. return 0;
  634. }
  635. if (result == NULL)
  636. return 0;
  637. for (i=0, count=-1; i<data->dl->entry_count; i++)
  638. {
  639. if (data->checked[i] == true)
  640. count++;
  641. if (count == get_result)
  642. {
  643. *result = data->dl->entries[i].name;
  644. return 0;
  645. }
  646. }
  647. return 1;
  648. }
  649. static int cstc_skins_condition(void)
  650. {
  651. struct directory_list *data;
  652. int i;
  653. data = Util_Dir_Read(va("%s/qw/skins/", com_basedir), 1, 1, skins_endings);
  654. if (data == NULL)
  655. return 0;
  656. if (data->entry_count == 0)
  657. {
  658. Com_Printf(va("no skins found in \"%s/qw/skins/\".\n", com_basedir));
  659. Util_Dir_Delete(data);
  660. i = 0;
  661. }
  662. else
  663. i = 1;
  664. Util_Dir_Delete(data);
  665. return i;
  666. }
  667. static void cstc_skins_draw(struct cst_info *self)
  668. {
  669. struct cstc_skindata *data;
  670. int x, y;
  671. int i, count;
  672. char *s;
  673. if (self->data == NULL)
  674. return;
  675. data = (struct cstc_skindata *) self->data;
  676. if (data->picture && ( self->toggleables_changed || self->selection_changed))
  677. {
  678. Draw_FreePicture(data->picture);
  679. data->picture = NULL;
  680. }
  681. if (data->picture == NULL)
  682. {
  683. for (i=0, count=-1; i<data->dl->entry_count; i++)
  684. {
  685. if (data->checked[i] == true)
  686. count++;
  687. if (count == self->selection)
  688. break;
  689. }
  690. if (i == data->dl->entry_count)
  691. return;
  692. data->picture = Draw_LoadPicture(va("skins/%s", data->dl->entries[i].name), DRAW_LOADPICTURE_NOFALLBACK);
  693. }
  694. if (data->picture == NULL)
  695. {
  696. s = va("not a valid skin.");
  697. x = vid.conwidth - strlen(s) * 8;
  698. y = self->offset_y;
  699. Draw_Fill(x, y, 8 * strlen(s), 8, 0);
  700. Draw_String(x, y, s);
  701. return;
  702. }
  703. y = self->offset_y - (self->direction == -1 ? 200: 0);
  704. x = vid.conwidth - 320;
  705. Draw_DrawPicture(data->picture, x, y, 320, 200);//, Draw_GetPictureWidth(self->picture), Draw_GetPictureHeight(self->picture));
  706. return;
  707. }
  708. void FS_Init()
  709. {
  710. CSTC_Add("enemyskin enemyquadskin enemypentskin enemybothskin teamskin teamquadskin teampentskin teambothskin", &cstc_skins_condition, &cstc_skins_get_results, &cstc_skins_get_data, &cstc_skins_draw, CSTC_MULTI_COMMAND| CSTC_NO_INPUT| CSTC_EXECUTE, "arrow up/down to navigate");
  711. Cmd_AddCommand("path", FS_Path_f);
  712. }