lucasgautheron

packages auto downloader WIP

Aug 24th, 2011
124
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Diff 52.58 KB | None | 0 0
  1. Index: client.cpp
  2. ===================================================================
  3. --- client.cpp  (revision 6612)
  4. +++ client.cpp  (working copy)
  5. @@ -813,3 +813,215 @@
  6.  COMMAND(listdemos, ARG_NONE);
  7.  COMMANDN(setmr, setminutesremaining, ARG_1INT);
  8.  COMMANDN(rewind, rewinddemo, ARG_1INT);
  9. +
  10. +// packages auto - downloader
  11. +
  12. +// arrays
  13. +vector<pckserver *> pckservers;
  14. +vector<package *> packages;
  15. +//vector<package *> pendingpackages;
  16. +
  17. +// thread
  18. +SDL_Thread *pckthread;
  19. +int process_packages(void *ptr);
  20. +
  21. +// cubescript
  22. +
  23. +void addpckserver(char *addr)
  24. +{
  25. +
  26. +}
  27. +
  28. +void addpackage(char *type, char *name)
  29. +{
  30. +    int t = atoi(type);
  31. +    if(t < 0 || t >= PCK_NUM) return;
  32. +    if(!name || name[0] == '\0') return;
  33. +    package *pck = new package();
  34. +    pck->name = newstring(name);
  35. +    pck->type = t;
  36. +    pck->source = pckservers[0]; // FIXME
  37. +    packages.add(pck);
  38. +}
  39. +
  40. +void requirepackage(int type, char *name)
  41. +{
  42. +    loopv(packages) if(packages[i]->type == type
  43. +        && !strcmp(packages[i]->name, name))
  44. +    {
  45. +        packages[i]->pending = true;
  46. +    }
  47. +}
  48. +
  49. +// reset current packages list
  50. +void resetpackages()
  51. +{
  52. +    loopv(packages)
  53. +    {
  54. +        DELETEP(packages[i]);
  55. +        packages.remove(i);
  56. +    }
  57. +    packages.shrink(0); // pointless?
  58. +}
  59. +
  60. +// update list of packages
  61. +void retrievepackages()
  62. +{
  63. +    resetpackages();
  64. +
  65. +    // TMP:
  66. +    if(!pckservers.length())
  67. +    {
  68. +        pckserver *srcserver = new pckserver();
  69. +        srcserver->addr = "http://de582.ispfr.net/cube";
  70. +        srcserver->pending = true;
  71. +        pckservers.add(srcserver);
  72. +    }
  73. +    // :TMP
  74. +    loopv(pckservers) pckservers[i]->pending = true;
  75. +    pckthread = SDL_CreateThread(process_packages, (void *)NULL);
  76. +}
  77. +
  78. +void downloadpackages()
  79. +{
  80. +    pckthread = SDL_CreateThread(process_packages, (void *)NULL);
  81. +}
  82. +
  83. +void test()
  84. +{
  85. +    loopi(10)
  86. +    {
  87. +        packages[260+i]->pending = true;
  88. +    }
  89. +    pckthread = SDL_CreateThread(process_packages, (void *)NULL);
  90. +}
  91. +
  92. +COMMAND(test, ARG_NONE);
  93. +COMMAND(addpckserver, ARG_1STR);
  94. +COMMAND(addpackage, ARG_2STR);
  95. +COMMAND(retrievepackages, ARG_NONE);
  96. +COMMAND(resetpackages, ARG_NONE);
  97. +
  98. +// cURL / Network
  99. +
  100. +static size_t write_callback(void *ptr, size_t size, size_t nmemb, FILE *stream)
  101. +{
  102. +    return fwrite(ptr, size, nmemb, stream);
  103. +}
  104. +
  105. +// update packages list from a source
  106. +void retrievepackages(pckserver *srcserver, FILE *outfile)
  107. +{
  108. +    srcserver->pending = false;
  109. +    string req;
  110. +    sprintf(req, "%s/get.php", srcserver->addr);
  111. +    conoutf("retrieving list of packages from %s...", srcserver->addr);
  112. +
  113. +    fprintf(outfile, "// setserver \"%s\";\n", srcserver->addr);
  114. +    
  115. +    CURL *curl = curl_easy_init();
  116. +    curl_easy_setopt(curl, CURLOPT_URL, req);
  117. +    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
  118. +    curl_easy_setopt(curl, CURLOPT_WRITEDATA, outfile);
  119. +    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "do=list");
  120. +    curl_easy_perform(curl);
  121. +    curl_easy_cleanup(curl);
  122. +}
  123. +
  124. +// download a package
  125. +bool dlpackage(package *pck)
  126. +{
  127. +    if(!pck || !pck->source) return false;
  128. +    FILE *outfile = fopen(path("config/tmp", true), "wb");
  129. +    string req;
  130. +    sprintf(req, "%s/get.php", pck->source->addr);
  131. +    string post;
  132. +    sprintf(post, "do=dl&name=%s&type=%d", pck->name, pck->type);
  133. +    conoutf("downloading %s...", pck->name);
  134. +
  135. +    CURL *curl = curl_easy_init();
  136. +    curl_easy_setopt(curl, CURLOPT_URL, req);
  137. +    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
  138. +    curl_easy_setopt(curl, CURLOPT_WRITEDATA, outfile);
  139. +    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post);
  140. +    curl_easy_perform(curl);
  141. +    curl_easy_cleanup(curl);
  142. +    fclose(outfile);
  143. +    return true;
  144. +}
  145. +
  146. +/*void retrievepackages()
  147. +{
  148. +    resetpackages();
  149. +    if(!pckservers.length())
  150. +    {
  151. +        pckserver *srcserver = new pckserver();
  152. +        srcserver->addr = "http://de582.ispfr.net/cube";
  153. +        pckservers.add(srcserver);
  154. +    }
  155. +    FILE *fp = fopen(path("config/packages.cfg", true), "w");
  156. +    loopv(pckservers)
  157. +    {
  158. +        retrievepackages(pckservers[i], fp);
  159. +    }
  160. +    fclose(fp);
  161. +    execfile("config/packages.cfg");
  162. +}*/
  163. +
  164. +// threading
  165. +
  166. +// threaded loop to process requests
  167. +int process_packages(void *ptr)
  168. +{
  169. +    FILE *fp = NULL;
  170. +    loopv(pckservers)
  171. +    {
  172. +        if(pckservers[i]->pending)
  173. +        {
  174. +            if(!fp) fp = fopen(path("config/packages.cfg", true), "w");
  175. +            retrievepackages(pckservers[i], fp);
  176. +        }
  177. +    }
  178. +    if(fp)
  179. +    {
  180. +        execfile("config/packages.cfg");
  181. +        conoutf("%d packages loaded !", packages.length());
  182. +        fclose(fp);
  183. +    }
  184. +
  185. +    bool dled = false;
  186. +    loopv(packages)
  187. +    {
  188. +        if(packages[i]->pending)
  189. +        {
  190. +            dled = true;
  191. +            packages[i]->pending = false; // even if an error occurs, do not dl again.
  192. +            if(!dlpackage(packages[i]))
  193. +            {
  194. +                conoutf("\f3failed!");
  195. +                continue;
  196. +            }
  197. +
  198. +            switch(packages[i]->type)
  199. +            {
  200. +                case PCK_TEXTURE:
  201. +                {
  202. +                    string tmp;
  203. +                    sprintf(tmp, "packages/textures/%s", packages[i]->name);
  204. +                    preparedir(tmp);
  205. +                    // with textures, the image itself is sent. Just need to copy it from the temporary file
  206. +                    if(!copyfile(path("config/tmp", true), tmp)) conoutf("\f3failed to install %s", packages[i]->name);
  207. +                }
  208. +                break;
  209. +
  210. +                default:
  211. +                {
  212. +                    conoutf("could not install package %s", packages[i]->name);
  213. +                }
  214. +                break;
  215. +            }
  216. +        }
  217. +    }
  218. +    if(dled) conoutf("packages installed.");
  219. +    return 0;
  220. +}
  221. \ No newline at end of file
  222. Index: entity.h
  223. ===================================================================
  224. --- entity.h    (revision 6612)
  225. +++ entity.h    (working copy)
  226. @@ -622,3 +622,23 @@
  227.  
  228.      return killmessages[gib?1:0][gun];
  229.  }
  230. +
  231. +struct pckserver
  232. +{
  233. +    char *addr;
  234. +    bool pending;
  235. +
  236. +    pckserver() : addr(NULL), pending(false) {}
  237. +};
  238. +
  239. +enum { PCK_TEXTURE, PCK_SKYBOX, PCK_MAPMODEL, PCK_AUDIO, PCK_MAP, PCK_NUM };
  240. +
  241. +struct package
  242. +{
  243. +    char *name;
  244. +    int type;
  245. +    bool pending;
  246. +    pckserver *source;
  247. +
  248. +    package() : name(NULL), type(-1), pending(false) {}
  249. +};
  250. \ No newline at end of file
  251. Index: platform.h
  252. ===================================================================
  253. --- platform.h  (revision 6612)
  254. +++ platform.h  (working copy)
  255. @@ -71,3 +71,8 @@
  256.  
  257.      #include <setjmp.h>
  258.  #endif
  259. +
  260. +#ifndef CURL_STATICLIB
  261. +#define CURL_STATICLIB
  262. +#endif
  263. +#include "curl/curl.h"
  264. \ No newline at end of file
  265. Index: protos.h
  266. ===================================================================
  267. --- protos.h    (revision 6612)
  268. +++ protos.h    (working copy)
  269. @@ -305,6 +305,9 @@
  270.  extern float skyfloor;
  271.  extern void draw_envbox(int fogdist);
  272.  
  273. +extern void requirepackage(int type, char *name);
  274. +extern void downloadpackages();
  275. +
  276.  extern int maxtmus;
  277.  extern void inittmus();
  278.  extern void resettmu(int n);
  279. Index: stream.cpp
  280. ===================================================================
  281. --- stream.cpp  (revision 6612)
  282. +++ stream.cpp  (working copy)
  283. @@ -1,785 +1,817 @@
  284. -#include "cube.h"
  285. -
  286. -///////////////////////// file system ///////////////////////
  287. -
  288. -#ifndef WIN32
  289. -#include <unistd.h>
  290. -#include <sys/stat.h>
  291. -#include <sys/types.h>
  292. -#include <dirent.h>
  293. -#endif
  294. -
  295. -string homedir = "";
  296. -vector<char *> packagedirs;
  297. -
  298. -char *makerelpath(const char *dir, const char *file, const char *prefix, const char *cmd)
  299. -{
  300. -    static string tmp;
  301. -    if(prefix) copystring(tmp, prefix);
  302. -    else tmp[0] = '\0';
  303. -    if(file[0]=='<')
  304. -    {
  305. -        const char *end = strrchr(file, '>');
  306. -        if(end)
  307. -        {
  308. -            size_t len = strlen(tmp);
  309. -            copystring(&tmp[len], file, min(sizeof(tmp)-len, size_t(end+2-file)));
  310. -            file = end+1;
  311. -        }
  312. -    }
  313. -    if(cmd) concatstring(tmp, cmd);
  314. -    defformatstring(pname)("%s/%s", dir, file);
  315. -    concatstring(tmp, pname);
  316. -    return tmp;
  317. -}
  318. -
  319. -
  320. -char *path(char *s)
  321. -{
  322. -    for(char *curpart = s;;)
  323. -    {
  324. -        char *endpart = strchr(curpart, '&');
  325. -        if(endpart) *endpart = '\0';
  326. -        if(curpart[0]=='<')
  327. -        {
  328. -            char *file = strrchr(curpart, '>');
  329. -            if(!file) return s;
  330. -            curpart = file+1;
  331. -        }
  332. -        for(char *t = curpart; (t = strpbrk(t, "/\\")); *t++ = PATHDIV);
  333. -        for(char *prevdir = NULL, *curdir = s;;)
  334. -        {
  335. -            prevdir = curdir[0]==PATHDIV ? curdir+1 : curdir;
  336. -            curdir = strchr(prevdir, PATHDIV);
  337. -            if(!curdir) break;
  338. -            if(prevdir+1==curdir && prevdir[0]=='.')
  339. -            {
  340. -                memmove(prevdir, curdir+1, strlen(curdir+1)+1);
  341. -                curdir = prevdir;
  342. -            }
  343. -            else if(curdir[1]=='.' && curdir[2]=='.' && curdir[3]==PATHDIV)
  344. -            {
  345. -                if(prevdir+2==curdir && prevdir[0]=='.' && prevdir[1]=='.') continue;
  346. -                memmove(prevdir, curdir+4, strlen(curdir+4)+1);
  347. -                curdir = prevdir;
  348. -            }
  349. -        }
  350. -        if(endpart)
  351. -        {
  352. -            *endpart = '&';
  353. -            curpart = endpart+1;
  354. -        }
  355. -        else break;
  356. -    }
  357. -    return s;
  358. -}
  359. -
  360. -char *path(const char *s, bool copy)
  361. -{
  362. -    static string tmp;
  363. -    copystring(tmp, s);
  364. -    path(tmp);
  365. -    return tmp;
  366. -}
  367. -
  368. -const char *parentdir(const char *directory)
  369. -{
  370. -    const char *p = directory + strlen(directory);
  371. -    while(p > directory && *p != '/' && *p != '\\') p--;
  372. -    static string parent;
  373. -    size_t len = p-directory+1;
  374. -    copystring(parent, directory, len);
  375. -    return parent;
  376. -}
  377. -
  378. -const char *behindpath(const char *s)
  379. -{
  380. -    const char *t = s;
  381. -    for( ; (s = strpbrk(s, "/\\")); t = ++s);
  382. -    return t;
  383. -}
  384. -
  385. -bool fileexists(const char *path, const char *mode)
  386. -{
  387. -    bool exists = true;
  388. -    if(mode[0]=='w' || mode[0]=='a') path = parentdir(path);
  389. -#ifdef WIN32
  390. -    if(GetFileAttributes(path) == INVALID_FILE_ATTRIBUTES) exists = false;
  391. -#else
  392. -    if(access(path, R_OK | (mode[0]=='w' || mode[0]=='a' ? W_OK : 0)) == -1) exists = false;
  393. -#endif
  394. -    return exists;
  395. -}
  396. -
  397. -bool createdir(const char *path)
  398. -{
  399. -    size_t len = strlen(path);
  400. -    if(path[len-1]==PATHDIV)
  401. -    {
  402. -        static string strip;
  403. -        path = copystring(strip, path, len);
  404. -    }
  405. -#ifdef WIN32
  406. -    return CreateDirectory(path, NULL)!=0;
  407. -#else
  408. -    return mkdir(path, 0777)==0;
  409. -#endif
  410. -}
  411. -
  412. -size_t fixpackagedir(char *dir)
  413. -{
  414. -    path(dir);
  415. -    size_t len = strlen(dir);
  416. -    if(len > 0 && dir[len-1] != PATHDIV)
  417. -    {
  418. -        dir[len] = PATHDIV;
  419. -        dir[len+1] = '\0';
  420. -    }
  421. -    return len;
  422. -}
  423. -
  424. -#ifdef WIN32
  425. -char *getregszvalue(HKEY root, const char *keystr, const char *query)
  426. -{
  427. -    HKEY key;
  428. -    if(RegOpenKeyEx(HKEY_CURRENT_USER, keystr, 0, KEY_READ, &key)==ERROR_SUCCESS)
  429. -    {
  430. -        DWORD type = 0, len = 0;
  431. -        if(RegQueryValueEx(key, query, 0, &type, 0, &len)==ERROR_SUCCESS && type==REG_SZ)
  432. -        {
  433. -            char *val = new char[len];
  434. -            long result = RegQueryValueEx(key, query, 0, &type, (uchar *)val, &len);
  435. -            if(result==ERROR_SUCCESS)
  436. -            {
  437. -                RegCloseKey(key);
  438. -                val[len-1] = '\0';
  439. -                return val;
  440. -            }
  441. -            delete[] val;
  442. -        }
  443. -        RegCloseKey(key);
  444. -    }
  445. -    return NULL;
  446. -}
  447. -#endif
  448. -
  449. -void sethomedir(const char *dir)
  450. -{
  451. -    string tmpdir;
  452. -    copystring(tmpdir, dir);
  453. -
  454. -#ifdef WIN32
  455. -    const char substitute[] = "?MYDOCUMENTS?";
  456. -    if(!strncmp(dir, substitute, strlen(substitute)))
  457. -    {
  458. -        const char *regpath = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders";
  459. -        char *mydocuments = getregszvalue(HKEY_CURRENT_USER, regpath, "Personal");
  460. -        if(mydocuments)
  461. -        {
  462. -            formatstring(tmpdir)("%s%s", mydocuments, dir+strlen(substitute));
  463. -            delete[] mydocuments;
  464. -        }
  465. -        else
  466. -        {
  467. -            printf("failed to retrieve 'Personal' path from '%s'\n", regpath);
  468. -        }
  469. -    }
  470. -#endif
  471. -
  472. -#ifndef STANDALONE
  473. -    clientlogf("Using home directory: %s", tmpdir);
  474. -#endif
  475. -
  476. -    if(fixpackagedir(tmpdir) > 0)
  477. -    {
  478. -        copystring(homedir, tmpdir);
  479. -        createdir(homedir);
  480. -    }
  481. -}
  482. -
  483. -void addpackagedir(const char *dir)
  484. -{
  485. -#ifndef STANDALONE
  486. -    clientlogf("Adding package directory: %s", dir);
  487. -#endif
  488. -
  489. -    string pdir;
  490. -    copystring(pdir, dir);
  491. -    if(fixpackagedir(pdir) > 0) packagedirs.add(newstring(pdir));
  492. -}
  493. -
  494. -const char *findfile(const char *filename, const char *mode)
  495. -{
  496. -    static string s;
  497. -    if(homedir[0])
  498. -    {
  499. -        formatstring(s)("%s%s", homedir, filename);
  500. -        if(fileexists(s, mode)) return s;
  501. -        if(mode[0]=='w' || mode[0]=='a')
  502. -        {
  503. -            string dirs;
  504. -            copystring(dirs, s);
  505. -            char *dir = strchr(dirs[0]==PATHDIV ? dirs+1 : dirs, PATHDIV);
  506. -            while(dir)
  507. -            {
  508. -                *dir = '\0';
  509. -                if(!fileexists(dirs, "r") && !createdir(dirs)) return s;
  510. -                *dir = PATHDIV;
  511. -                dir = strchr(dir+1, PATHDIV);
  512. -            }
  513. -            return s;
  514. -        }
  515. -    }
  516. -    if(mode[0]=='w' || mode[0]=='a') return filename;
  517. -    loopv(packagedirs)
  518. -    {
  519. -        formatstring(s)("%s%s", packagedirs[i], filename);
  520. -        if(fileexists(s, mode)) return s;
  521. -    }
  522. -    return filename;
  523. -}
  524. -
  525. -bool listdir(const char *dir, const char *ext, vector<char *> &files)
  526. -{
  527. -    int extsize = ext ? (int)strlen(ext)+1 : 0;
  528. -    #if defined(WIN32)
  529. -    defformatstring(pathname)("%s\\*.%s", dir, ext ? ext : "*");
  530. -    WIN32_FIND_DATA FindFileData;
  531. -    HANDLE Find = FindFirstFile(path(pathname), &FindFileData);
  532. -    if(Find != INVALID_HANDLE_VALUE)
  533. -    {
  534. -        do {
  535. -            files.add(newstring(FindFileData.cFileName, (int)strlen(FindFileData.cFileName) - extsize));
  536. -        } while(FindNextFile(Find, &FindFileData));
  537. -        FindClose(Find);
  538. -        return true;
  539. -    }
  540. -    #else
  541. -    string pathname;
  542. -    copystring(pathname, dir);
  543. -    DIR *d = opendir(path(pathname));
  544. -    if(d)
  545. -    {
  546. -        struct dirent *de;
  547. -        while((de = readdir(d)) != NULL)
  548. -        {
  549. -            if(!ext) files.add(newstring(de->d_name));
  550. -            else
  551. -            {
  552. -                int namelength = (int)strlen(de->d_name) - extsize;
  553. -                if(namelength > 0 && de->d_name[namelength] == '.' && strncmp(de->d_name+namelength+1, ext, extsize-1)==0)
  554. -                    files.add(newstring(de->d_name, namelength));
  555. -            }
  556. -        }
  557. -        closedir(d);
  558. -        return true;
  559. -    }
  560. -    #endif
  561. -    else return false;
  562. -}
  563. -
  564. -int listfiles(const char *dir, const char *ext, vector<char *> &files)
  565. -{
  566. -    int dirs = 0;
  567. -    if(listdir(dir, ext, files)) dirs++;
  568. -    string s;
  569. -    if(homedir[0])
  570. -    {
  571. -        formatstring(s)("%s%s", homedir, dir);
  572. -        if(listdir(s, ext, files)) dirs++;
  573. -    }
  574. -    loopv(packagedirs)
  575. -    {
  576. -        formatstring(s)("%s%s", packagedirs[i], dir);
  577. -        if(listdir(s, ext, files)) dirs++;
  578. -    }
  579. -#ifndef STANDALONE
  580. -    dirs += listzipfiles(dir, ext, files);
  581. -#endif
  582. -    return dirs;
  583. -}
  584. -
  585. -bool delfile(const char *path)
  586. -{
  587. -    return !remove(path);
  588. -}
  589. -
  590. -#ifndef STANDALONE
  591. -static int rwopsseek(SDL_RWops *rw, int offset, int whence)
  592. -{
  593. -    stream *f = (stream *)rw->hidden.unknown.data1;
  594. -    if((!offset && whence==SEEK_CUR) || f->seek(offset, whence)) return f->tell();
  595. -    return -1;
  596. -}
  597. -
  598. -static int rwopsread(SDL_RWops *rw, void *buf, int size, int nmemb)
  599. -{
  600. -    stream *f = (stream *)rw->hidden.unknown.data1;
  601. -    return f->read(buf, size*nmemb)/size;
  602. -}
  603. -
  604. -static int rwopswrite(SDL_RWops *rw, const void *buf, int size, int nmemb)
  605. -{
  606. -    stream *f = (stream *)rw->hidden.unknown.data1;
  607. -    return f->write(buf, size*nmemb)/size;
  608. -}
  609. -
  610. -static int rwopsclose(SDL_RWops *rw)
  611. -{
  612. -    return 0;
  613. -}
  614. -
  615. -SDL_RWops *stream::rwops()
  616. -{
  617. -    SDL_RWops *rw = SDL_AllocRW();
  618. -    if(!rw) return NULL;
  619. -    rw->hidden.unknown.data1 = this;
  620. -    rw->seek = rwopsseek;
  621. -    rw->read = rwopsread;
  622. -    rw->write = rwopswrite;
  623. -    rw->close = rwopsclose;
  624. -    return rw;
  625. -}
  626. -#endif
  627. -
  628. -long stream::size()
  629. -{
  630. -    long pos = tell(), endpos;
  631. -    if(pos < 0 || !seek(0, SEEK_END)) return -1;
  632. -    endpos = tell();
  633. -    return pos == endpos || seek(pos, SEEK_SET) ? endpos : -1;
  634. -}
  635. -
  636. -bool stream::getline(char *str, int len)
  637. -{
  638. -    loopi(len-1)
  639. -    {
  640. -        if(read(&str[i], 1) != 1) { str[i] = '\0'; return i > 0; }
  641. -        else if(str[i] == '\n') { str[i+1] = '\0'; return true; }
  642. -    }
  643. -    if(len > 0) str[len-1] = '\0';
  644. -    return true;
  645. -}
  646. -
  647. -#ifdef __linux__
  648. -#include <sys/statvfs.h>
  649. -#define MINFSSIZE 50000000ull           // 50MB
  650. -#endif
  651. -
  652. -struct filestream : stream
  653. -{
  654. -    FILE *file;
  655. -
  656. -    filestream() : file(NULL) {}
  657. -    ~filestream() { close(); }
  658. -
  659. -    bool open(const char *name, const char *mode)
  660. -    {
  661. -        if(file) return false;
  662. -        file = fopen(name, mode);
  663. -#ifdef __linux__
  664. -        struct statvfs buf;
  665. -        if(file && strchr(mode,'w'))
  666. -        {
  667. -            int fail = fstatvfs(fileno(file), &buf);
  668. -            if (fail || (unsigned long long)buf.f_frsize * (unsigned long long)buf.f_bavail < MINFSSIZE)
  669. -            {
  670. -                close();
  671. -                return false;
  672. -            }
  673. -        }
  674. -#endif
  675. -        return file!=NULL;
  676. -    }
  677. -
  678. -    bool opentemp(const char *name, const char *mode)
  679. -    {
  680. -        if(file) return false;
  681. -#ifdef WIN32
  682. -        file = fopen(name, mode);
  683. -#else
  684. -        file = tmpfile();
  685. -#endif
  686. -        return file!=NULL;
  687. -    }
  688. -
  689. -    void close()
  690. -    {
  691. -        if(file) { fclose(file); file = NULL; }
  692. -    }
  693. -
  694. -    bool end() { return feof(file)!=0; }
  695. -    long tell() { return ftell(file); }
  696. -    bool seek(long offset, int whence) { return fseek(file, offset, whence) >= 0; }
  697. -    int read(void *buf, int len) { return (int)fread(buf, 1, len, file); }
  698. -    int write(const void *buf, int len) { return (int)fwrite(buf, 1, len, file); }
  699. -    int getchar() { return fgetc(file); }
  700. -    bool putchar(int c) { return fputc(c, file)!=EOF; }
  701. -    bool getline(char *str, int len) { return fgets(str, len, file)!=NULL; }
  702. -    bool putstring(const char *str) { return fputs(str, file)!=EOF; }
  703. -
  704. -    int printf(const char *fmt, ...)
  705. -    {
  706. -        va_list v;
  707. -        va_start(v, fmt);
  708. -        int result = vfprintf(file, fmt, v);
  709. -        va_end(v);
  710. -        return result;
  711. -    }
  712. -};
  713. -
  714. -#ifndef STANDALONE
  715. -VAR(dbggz, 0, 0, 1);
  716. -#endif
  717. -
  718. -struct gzstream : stream
  719. -{
  720. -    enum
  721. -    {
  722. -        MAGIC1   = 0x1F,
  723. -        MAGIC2   = 0x8B,
  724. -        BUFSIZE  = 16384,
  725. -        OS_UNIX  = 0x03
  726. -    };
  727. -
  728. -    enum
  729. -    {
  730. -        F_ASCII    = 0x01,
  731. -        F_CRC      = 0x02,
  732. -        F_EXTRA    = 0x04,
  733. -        F_NAME     = 0x08,
  734. -        F_COMMENT  = 0x10,
  735. -        F_RESERVED = 0xE0
  736. -    };
  737. -
  738. -    stream *file;
  739. -    z_stream zfile;
  740. -    uchar *buf;
  741. -    bool reading, writing, autoclose;
  742. -    uint crc;
  743. -    int headersize;
  744. -
  745. -    gzstream() : file(NULL), buf(NULL), reading(false), writing(false), autoclose(false), crc(0), headersize(0)
  746. -    {
  747. -        zfile.zalloc = NULL;
  748. -        zfile.zfree = NULL;
  749. -        zfile.opaque = NULL;
  750. -        zfile.next_in = zfile.next_out = NULL;
  751. -        zfile.avail_in = zfile.avail_out = 0;
  752. -    }
  753. -
  754. -    ~gzstream()
  755. -    {
  756. -        close();
  757. -    }
  758. -
  759. -    void writeheader()
  760. -    {
  761. -        uchar header[] = { MAGIC1, MAGIC2, Z_DEFLATED, 0, 0, 0, 0, 0, 0, OS_UNIX };
  762. -        file->write(header, sizeof(header));
  763. -    }
  764. -
  765. -    void readbuf(int size = BUFSIZE)
  766. -    {
  767. -        if(!zfile.avail_in) zfile.next_in = (Bytef *)buf;
  768. -        size = min(size, int(&buf[BUFSIZE] - &zfile.next_in[zfile.avail_in]));
  769. -        int n = file->read(zfile.next_in + zfile.avail_in, size);
  770. -        if(n > 0) zfile.avail_in += n;
  771. -    }
  772. -
  773. -    int readbyte(int size = BUFSIZE)
  774. -    {
  775. -        if(!zfile.avail_in) readbuf(size);
  776. -        if(!zfile.avail_in) return 0;
  777. -        zfile.avail_in--;
  778. -        return *(uchar *)zfile.next_in++;
  779. -    }
  780. -
  781. -    void skipbytes(int n)
  782. -    {
  783. -        while(n > 0 && zfile.avail_in > 0)
  784. -        {
  785. -            int skipped = min(n, (int)zfile.avail_in);
  786. -            zfile.avail_in -= skipped;
  787. -            zfile.next_in += skipped;
  788. -            n -= skipped;
  789. -        }
  790. -        if(n <= 0) return;
  791. -        file->seek(n, SEEK_CUR);
  792. -    }
  793. -
  794. -    bool checkheader()
  795. -    {
  796. -        readbuf(10);
  797. -        if(readbyte() != MAGIC1 || readbyte() != MAGIC2 || readbyte() != Z_DEFLATED) return false;
  798. -        int flags = readbyte();
  799. -        if(flags & F_RESERVED) return false;
  800. -        skipbytes(6);
  801. -        if(flags & F_EXTRA)
  802. -        {
  803. -            int len = readbyte(512);
  804. -            len |= readbyte(512)<<8;
  805. -            skipbytes(len);
  806. -        }
  807. -        if(flags & F_NAME) while(readbyte(512));
  808. -        if(flags & F_COMMENT) while(readbyte(512));
  809. -        if(flags & F_CRC) skipbytes(2);
  810. -        headersize = file->tell() - zfile.avail_in;
  811. -        return zfile.avail_in > 0 || !file->end();
  812. -    }
  813. -
  814. -    bool open(stream *f, const char *mode, bool needclose, int level)
  815. -    {
  816. -        if(file) return false;
  817. -        for(; *mode; mode++)
  818. -        {
  819. -            if(*mode=='r') { reading = true; break; }
  820. -            else if(*mode=='w') { writing = true; break; }
  821. -        }
  822. -        if(reading)
  823. -        {
  824. -            if(inflateInit2(&zfile, -MAX_WBITS) != Z_OK) reading = false;
  825. -        }
  826. -        else if(writing && deflateInit2(&zfile, level, Z_DEFLATED, -MAX_WBITS, min(MAX_MEM_LEVEL, 8), Z_DEFAULT_STRATEGY) != Z_OK) writing = false;
  827. -        if(!reading && !writing) return false;
  828. -
  829. -        autoclose = needclose;
  830. -        file = f;
  831. -        crc = crc32(0, NULL, 0);
  832. -        buf = new uchar[BUFSIZE];
  833. -
  834. -        if(reading)
  835. -        {
  836. -            if(!checkheader()) { stopreading(); return false; }
  837. -        }
  838. -        else if(writing) writeheader();
  839. -        return true;
  840. -    }
  841. -
  842. -    uint getcrc() { return crc; }
  843. -
  844. -    void finishreading()
  845. -    {
  846. -        if(!reading) return;
  847. -#ifndef STANDALONE
  848. -        if(dbggz)
  849. -        {
  850. -            uint checkcrc = 0, checksize = 0;
  851. -            loopi(4) checkcrc |= uint(readbyte()) << (i*8);
  852. -            loopi(4) checksize |= uint(readbyte()) << (i*8);
  853. -            if(checkcrc != crc)
  854. -                conoutf("gzip crc check failed: read %X, calculated %X", checkcrc, crc);
  855. -            if(checksize != zfile.total_out)
  856. -                conoutf("gzip size check failed: read %d, calculated %d", checksize, zfile.total_out);
  857. -        }
  858. -#endif
  859. -    }
  860. -
  861. -    void stopreading()
  862. -    {
  863. -        if(!reading) return;
  864. -        inflateEnd(&zfile);
  865. -        reading = false;
  866. -    }
  867. -
  868. -    void finishwriting()
  869. -    {
  870. -        if(!writing) return;
  871. -        for(;;)
  872. -        {
  873. -            int err = zfile.avail_out > 0 ? deflate(&zfile, Z_FINISH) : Z_OK;
  874. -            if(err != Z_OK && err != Z_STREAM_END) break;
  875. -            flush();
  876. -            if(err == Z_STREAM_END) break;
  877. -        }
  878. -        uchar trailer[8] =
  879. -        {
  880. -            crc&0xFF, (crc>>8)&0xFF, (crc>>16)&0xFF, (crc>>24)&0xFF,
  881. -            zfile.total_in&0xFF, (zfile.total_in>>8)&0xFF, (zfile.total_in>>16)&0xFF, (zfile.total_in>>24)&0xFF
  882. -        };
  883. -        file->write(trailer, sizeof(trailer));
  884. -    }
  885. -
  886. -    void stopwriting()
  887. -    {
  888. -        if(!writing) return;
  889. -        deflateEnd(&zfile);
  890. -        writing = false;
  891. -    }
  892. -
  893. -    void close()
  894. -    {
  895. -        if(reading) finishreading();
  896. -        stopreading();
  897. -        if(writing) finishwriting();
  898. -        stopwriting();
  899. -        DELETEA(buf);
  900. -        if(autoclose) DELETEP(file);
  901. -    }
  902. -
  903. -    bool end() { return !reading && !writing; }
  904. -    long tell() { return reading ? zfile.total_out : (writing ? zfile.total_in : -1); }
  905. -
  906. -    bool seek(long offset, int whence)
  907. -    {
  908. -        if(writing || !reading) return false;
  909. -
  910. -        if(whence == SEEK_END)
  911. -        {
  912. -            uchar skip[512];
  913. -            while(read(skip, sizeof(skip)) == sizeof(skip));
  914. -            return !offset;
  915. -        }
  916. -        else if(whence == SEEK_CUR) offset += zfile.total_out;
  917. -
  918. -        if(offset >= (int)zfile.total_out) offset -= zfile.total_out;
  919. -        else if(offset < 0 || !file->seek(headersize, SEEK_SET)) return false;
  920. -        else
  921. -        {
  922. -            if(zfile.next_in && zfile.total_in <= uint(zfile.next_in - buf))
  923. -            {
  924. -                zfile.avail_in += zfile.total_in;
  925. -                zfile.next_in -= zfile.total_in;
  926. -            }
  927. -            else
  928. -            {
  929. -                zfile.avail_in = 0;
  930. -                zfile.next_in = NULL;
  931. -            }
  932. -            inflateReset(&zfile);
  933. -            crc = crc32(0, NULL, 0);
  934. -        }
  935. -
  936. -        uchar skip[512];
  937. -        while(offset > 0)
  938. -        {
  939. -            int skipped = min(offset, (long)sizeof(skip));
  940. -            if(read(skip, skipped) != skipped) { stopreading(); return false; }
  941. -            offset -= skipped;
  942. -        }
  943. -
  944. -        return true;
  945. -    }
  946. -
  947. -    int read(void *buf, int len)
  948. -    {
  949. -        if(!reading || !buf || !len) return 0;
  950. -        zfile.next_out = (Bytef *)buf;
  951. -        zfile.avail_out = len;
  952. -        while(zfile.avail_out > 0)
  953. -        {
  954. -            if(!zfile.avail_in)
  955. -            {
  956. -                readbuf(BUFSIZE);
  957. -                if(!zfile.avail_in) { stopreading(); break; }
  958. -            }
  959. -            int err = inflate(&zfile, Z_NO_FLUSH);
  960. -            if(err == Z_STREAM_END) { crc = crc32(crc, (Bytef *)buf, len - zfile.avail_out); finishreading(); stopreading(); return len - zfile.avail_out; }
  961. -            else if(err != Z_OK) { stopreading(); break; }
  962. -        }
  963. -        crc = crc32(crc, (Bytef *)buf, len - zfile.avail_out);
  964. -        return len - zfile.avail_out;
  965. -    }
  966. -
  967. -    bool flush()
  968. -    {
  969. -        if(zfile.next_out && zfile.avail_out < BUFSIZE)
  970. -        {
  971. -            if(file->write(buf, BUFSIZE - zfile.avail_out) != int(BUFSIZE - zfile.avail_out))
  972. -                return false;
  973. -        }
  974. -        zfile.next_out = buf;
  975. -        zfile.avail_out = BUFSIZE;
  976. -        return true;
  977. -    }
  978. -
  979. -    int write(const void *buf, int len)
  980. -    {
  981. -        if(!writing || !buf || !len) return 0;
  982. -        zfile.next_in = (Bytef *)buf;
  983. -        zfile.avail_in = len;
  984. -        while(zfile.avail_in > 0)
  985. -        {
  986. -            if(!zfile.avail_out && !flush()) { stopwriting(); break; }
  987. -            int err = deflate(&zfile, Z_NO_FLUSH);
  988. -            if(err != Z_OK) { stopwriting(); break; }
  989. -        }
  990. -        crc = crc32(crc, (Bytef *)buf, len - zfile.avail_in);
  991. -        return len - zfile.avail_in;
  992. -    }
  993. -};
  994. -
  995. -
  996. -stream *openrawfile(const char *filename, const char *mode)
  997. -{
  998. -    const char *found = findfile(filename, mode);
  999. -#ifndef STANDALONE
  1000. -    if(mode && (mode[0]=='w' || mode[0]=='a')) conoutf("writing to file: %s", found);
  1001. -#endif
  1002. -    if(!found) return NULL;
  1003. -    filestream *file = new filestream;
  1004. -    if(!file->open(found, mode))
  1005. -    {
  1006. -#ifndef STANDALONE
  1007. -//         conoutf("file failure! %s",filename);
  1008. -#endif
  1009. -        delete file; return NULL;
  1010. -    }
  1011. -    return file;
  1012. -}
  1013. -
  1014. -stream *openfile(const char *filename, const char *mode)
  1015. -{
  1016. -#ifndef STANDALONE
  1017. -    stream *s = openzipfile(filename, mode);
  1018. -    if(s) return s;
  1019. -#endif
  1020. -    return openrawfile(filename, mode);
  1021. -}
  1022. -
  1023. -int getfilesize(const char *filename)
  1024. -{
  1025. -    stream *f = openfile(filename, "rb");
  1026. -    if(!f) return -1;
  1027. -    int len = f->size();
  1028. -    delete f;
  1029. -    return len;
  1030. -}
  1031. -
  1032. -stream *opentempfile(const char *name, const char *mode)
  1033. -{
  1034. -    const char *found = findfile(name, mode);
  1035. -    filestream *file = new filestream;
  1036. -    if(!file->opentemp(found ? found : name, mode)) { delete file; return NULL; }
  1037. -    return file;
  1038. -}
  1039. -
  1040. -stream *opengzfile(const char *filename, const char *mode, stream *file, int level)
  1041. -{
  1042. -    stream *source = file ? file : openfile(filename, mode);
  1043. -    if(!source) return NULL;
  1044. -    gzstream *gz = new gzstream;
  1045. -    if(!gz->open(source, mode, !file, level)) { if(!file) delete source; return NULL; }
  1046. -    return gz;
  1047. -}
  1048. -
  1049. -char *loadfile(const char *fn, int *size, const char *mode)
  1050. -{
  1051. -    stream *f = openfile(fn, mode ? mode : "rb");
  1052. -    if(!f) return NULL;
  1053. -    int len = f->size();
  1054. -    if(len<=0) { delete f; return NULL; }
  1055. -    char *buf = new char[len+1];
  1056. -    if(!buf) { delete f; return NULL; }
  1057. -    buf[len] = 0;
  1058. -    int rlen = f->read(buf, len);
  1059. -    delete f;
  1060. -    if(len!=rlen && (!mode || strchr(mode, 'b')))
  1061. -    {
  1062. -        delete[] buf;
  1063. -        return NULL;
  1064. -    }
  1065. -    if(size!=NULL) *size = len;
  1066. -    return buf;
  1067. -}
  1068. -
  1069. +#include "cube.h"
  1070. +
  1071. +///////////////////////// file system ///////////////////////
  1072. +
  1073. +#ifndef WIN32
  1074. +#include <unistd.h>
  1075. +#include <sys/stat.h>
  1076. +#include <sys/types.h>
  1077. +#include <dirent.h>
  1078. +#endif
  1079. +
  1080. +string homedir = "";
  1081. +vector<char *> packagedirs;
  1082. +
  1083. +char *makerelpath(const char *dir, const char *file, const char *prefix, const char *cmd)
  1084. +{
  1085. +    static string tmp;
  1086. +    if(prefix) copystring(tmp, prefix);
  1087. +    else tmp[0] = '\0';
  1088. +    if(file[0]=='<')
  1089. +    {
  1090. +        const char *end = strrchr(file, '>');
  1091. +        if(end)
  1092. +        {
  1093. +            size_t len = strlen(tmp);
  1094. +            copystring(&tmp[len], file, min(sizeof(tmp)-len, size_t(end+2-file)));
  1095. +            file = end+1;
  1096. +        }
  1097. +    }
  1098. +    if(cmd) concatstring(tmp, cmd);
  1099. +    defformatstring(pname)("%s/%s", dir, file);
  1100. +    concatstring(tmp, pname);
  1101. +    return tmp;
  1102. +}
  1103. +
  1104. +
  1105. +char *path(char *s)
  1106. +{
  1107. +    for(char *curpart = s;;)
  1108. +    {
  1109. +        char *endpart = strchr(curpart, '&');
  1110. +        if(endpart) *endpart = '\0';
  1111. +        if(curpart[0]=='<')
  1112. +        {
  1113. +            char *file = strrchr(curpart, '>');
  1114. +            if(!file) return s;
  1115. +            curpart = file+1;
  1116. +        }
  1117. +        for(char *t = curpart; (t = strpbrk(t, "/\\")); *t++ = PATHDIV);
  1118. +        for(char *prevdir = NULL, *curdir = s;;)
  1119. +        {
  1120. +            prevdir = curdir[0]==PATHDIV ? curdir+1 : curdir;
  1121. +            curdir = strchr(prevdir, PATHDIV);
  1122. +            if(!curdir) break;
  1123. +            if(prevdir+1==curdir && prevdir[0]=='.')
  1124. +            {
  1125. +                memmove(prevdir, curdir+1, strlen(curdir+1)+1);
  1126. +                curdir = prevdir;
  1127. +            }
  1128. +            else if(curdir[1]=='.' && curdir[2]=='.' && curdir[3]==PATHDIV)
  1129. +            {
  1130. +                if(prevdir+2==curdir && prevdir[0]=='.' && prevdir[1]=='.') continue;
  1131. +                memmove(prevdir, curdir+4, strlen(curdir+4)+1);
  1132. +                curdir = prevdir;
  1133. +            }
  1134. +        }
  1135. +        if(endpart)
  1136. +        {
  1137. +            *endpart = '&';
  1138. +            curpart = endpart+1;
  1139. +        }
  1140. +        else break;
  1141. +    }
  1142. +    return s;
  1143. +}
  1144. +
  1145. +char *path(const char *s, bool copy)
  1146. +{
  1147. +    static string tmp;
  1148. +    copystring(tmp, s);
  1149. +    path(tmp);
  1150. +    return tmp;
  1151. +}
  1152. +
  1153. +const char *parentdir(const char *directory)
  1154. +{
  1155. +    const char *p = directory + strlen(directory);
  1156. +    while(p > directory && *p != '/' && *p != '\\') p--;
  1157. +    static string parent;
  1158. +    size_t len = p-directory+1;
  1159. +    copystring(parent, directory, len);
  1160. +    return parent;
  1161. +}
  1162. +
  1163. +const char *behindpath(const char *s)
  1164. +{
  1165. +    const char *t = s;
  1166. +    for( ; (s = strpbrk(s, "/\\")); t = ++s);
  1167. +    return t;
  1168. +}
  1169. +
  1170. +bool fileexists(const char *path, const char *mode)
  1171. +{
  1172. +    bool exists = true;
  1173. +    if(mode[0]=='w' || mode[0]=='a') path = parentdir(path);
  1174. +#ifdef WIN32
  1175. +    if(GetFileAttributes(path) == INVALID_FILE_ATTRIBUTES) exists = false;
  1176. +#else
  1177. +    if(access(path, R_OK | (mode[0]=='w' || mode[0]=='a' ? W_OK : 0)) == -1) exists = false;
  1178. +#endif
  1179. +    return exists;
  1180. +}
  1181. +
  1182. +bool createdir(const char *path)
  1183. +{
  1184. +    size_t len = strlen(path);
  1185. +    if(path[len-1]==PATHDIV)
  1186. +    {
  1187. +        static string strip;
  1188. +        path = copystring(strip, path, len);
  1189. +    }
  1190. +#ifdef WIN32
  1191. +    return CreateDirectory(path, NULL)!=0;
  1192. +#else
  1193. +    return mkdir(path, 0777)==0;
  1194. +#endif
  1195. +}
  1196. +
  1197. +size_t fixpackagedir(char *dir)
  1198. +{
  1199. +    path(dir);
  1200. +    size_t len = strlen(dir);
  1201. +    if(len > 0 && dir[len-1] != PATHDIV)
  1202. +    {
  1203. +        dir[len] = PATHDIV;
  1204. +        dir[len+1] = '\0';
  1205. +    }
  1206. +    return len;
  1207. +}
  1208. +
  1209. +#ifdef WIN32
  1210. +char *getregszvalue(HKEY root, const char *keystr, const char *query)
  1211. +{
  1212. +    HKEY key;
  1213. +    if(RegOpenKeyEx(HKEY_CURRENT_USER, keystr, 0, KEY_READ, &key)==ERROR_SUCCESS)
  1214. +    {
  1215. +        DWORD type = 0, len = 0;
  1216. +        if(RegQueryValueEx(key, query, 0, &type, 0, &len)==ERROR_SUCCESS && type==REG_SZ)
  1217. +        {
  1218. +            char *val = new char[len];
  1219. +            long result = RegQueryValueEx(key, query, 0, &type, (uchar *)val, &len);
  1220. +            if(result==ERROR_SUCCESS)
  1221. +            {
  1222. +                RegCloseKey(key);
  1223. +                val[len-1] = '\0';
  1224. +                return val;
  1225. +            }
  1226. +            delete[] val;
  1227. +        }
  1228. +        RegCloseKey(key);
  1229. +    }
  1230. +    return NULL;
  1231. +}
  1232. +#endif
  1233. +
  1234. +void sethomedir(const char *dir)
  1235. +{
  1236. +    string tmpdir;
  1237. +    copystring(tmpdir, dir);
  1238. +
  1239. +#ifdef WIN32
  1240. +    const char substitute[] = "?MYDOCUMENTS?";
  1241. +    if(!strncmp(dir, substitute, strlen(substitute)))
  1242. +    {
  1243. +        const char *regpath = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders";
  1244. +        char *mydocuments = getregszvalue(HKEY_CURRENT_USER, regpath, "Personal");
  1245. +        if(mydocuments)
  1246. +        {
  1247. +            formatstring(tmpdir)("%s%s", mydocuments, dir+strlen(substitute));
  1248. +            delete[] mydocuments;
  1249. +        }
  1250. +        else
  1251. +        {
  1252. +            printf("failed to retrieve 'Personal' path from '%s'\n", regpath);
  1253. +        }
  1254. +    }
  1255. +#endif
  1256. +
  1257. +#ifndef STANDALONE
  1258. +    clientlogf("Using home directory: %s", tmpdir);
  1259. +#endif
  1260. +
  1261. +    if(fixpackagedir(tmpdir) > 0)
  1262. +    {
  1263. +        copystring(homedir, tmpdir);
  1264. +        createdir(homedir);
  1265. +    }
  1266. +}
  1267. +
  1268. +void addpackagedir(const char *dir)
  1269. +{
  1270. +#ifndef STANDALONE
  1271. +    clientlogf("Adding package directory: %s", dir);
  1272. +#endif
  1273. +
  1274. +    string pdir;
  1275. +    copystring(pdir, dir);
  1276. +    if(fixpackagedir(pdir) > 0) packagedirs.add(newstring(pdir));
  1277. +}
  1278. +
  1279. +const char *findfile(const char *filename, const char *mode)
  1280. +{
  1281. +    static string s;
  1282. +    if(homedir[0])
  1283. +    {
  1284. +        formatstring(s)("%s%s", homedir, filename);
  1285. +        if(fileexists(s, mode)) return s;
  1286. +        if(mode[0]=='w' || mode[0]=='a')
  1287. +        {
  1288. +            string dirs;
  1289. +            copystring(dirs, s);
  1290. +            char *dir = strchr(dirs[0]==PATHDIV ? dirs+1 : dirs, PATHDIV);
  1291. +            while(dir)
  1292. +            {
  1293. +                *dir = '\0';
  1294. +                if(!fileexists(dirs, "r") && !createdir(dirs)) return s;
  1295. +                *dir = PATHDIV;
  1296. +                dir = strchr(dir+1, PATHDIV);
  1297. +            }
  1298. +            return s;
  1299. +        }
  1300. +    }
  1301. +    if(mode[0]=='w' || mode[0]=='a') return filename;
  1302. +    loopv(packagedirs)
  1303. +    {
  1304. +        formatstring(s)("%s%s", packagedirs[i], filename);
  1305. +        if(fileexists(s, mode)) return s;
  1306. +    }
  1307. +    return filename;
  1308. +}
  1309. +
  1310. +bool listdir(const char *dir, const char *ext, vector<char *> &files)
  1311. +{
  1312. +    int extsize = ext ? (int)strlen(ext)+1 : 0;
  1313. +    #if defined(WIN32)
  1314. +    defformatstring(pathname)("%s\\*.%s", dir, ext ? ext : "*");
  1315. +    WIN32_FIND_DATA FindFileData;
  1316. +    HANDLE Find = FindFirstFile(path(pathname), &FindFileData);
  1317. +    if(Find != INVALID_HANDLE_VALUE)
  1318. +    {
  1319. +        do {
  1320. +            files.add(newstring(FindFileData.cFileName, (int)strlen(FindFileData.cFileName) - extsize));
  1321. +        } while(FindNextFile(Find, &FindFileData));
  1322. +        FindClose(Find);
  1323. +        return true;
  1324. +    }
  1325. +    #else
  1326. +    string pathname;
  1327. +    copystring(pathname, dir);
  1328. +    DIR *d = opendir(path(pathname));
  1329. +    if(d)
  1330. +    {
  1331. +        struct dirent *de;
  1332. +        while((de = readdir(d)) != NULL)
  1333. +        {
  1334. +            if(!ext) files.add(newstring(de->d_name));
  1335. +            else
  1336. +            {
  1337. +                int namelength = (int)strlen(de->d_name) - extsize;
  1338. +                if(namelength > 0 && de->d_name[namelength] == '.' && strncmp(de->d_name+namelength+1, ext, extsize-1)==0)
  1339. +                    files.add(newstring(de->d_name, namelength));
  1340. +            }
  1341. +        }
  1342. +        closedir(d);
  1343. +        return true;
  1344. +    }
  1345. +    #endif
  1346. +    else return false;
  1347. +}
  1348. +
  1349. +int listfiles(const char *dir, const char *ext, vector<char *> &files)
  1350. +{
  1351. +    int dirs = 0;
  1352. +    if(listdir(dir, ext, files)) dirs++;
  1353. +    string s;
  1354. +    if(homedir[0])
  1355. +    {
  1356. +        formatstring(s)("%s%s", homedir, dir);
  1357. +        if(listdir(s, ext, files)) dirs++;
  1358. +    }
  1359. +    loopv(packagedirs)
  1360. +    {
  1361. +        formatstring(s)("%s%s", packagedirs[i], dir);
  1362. +        if(listdir(s, ext, files)) dirs++;
  1363. +    }
  1364. +#ifndef STANDALONE
  1365. +    dirs += listzipfiles(dir, ext, files);
  1366. +#endif
  1367. +    return dirs;
  1368. +}
  1369. +
  1370. +bool delfile(const char *path)
  1371. +{
  1372. +    return !remove(path);
  1373. +}
  1374. +
  1375. +bool copyfile(const char *source, const char *destination)
  1376. +{
  1377. +    FILE *from = fopen(source, "rb");
  1378. +    FILE *dest = fopen(destination, "wb");
  1379. +    
  1380. +    if(!from) { return false; }
  1381. +    if(!dest) { return false; }
  1382. +    size_t len;
  1383. +    uchar buf[1024];
  1384. +    while(len = fread(&buf, sizeof(uchar), 1024, from))
  1385. +    {
  1386. +        fwrite(&buf, sizeof(uchar), len, dest);
  1387. +    }
  1388. +    fclose(from);
  1389. +    fclose(dest);
  1390. +    return true;
  1391. +}
  1392. +
  1393. +bool preparedir(const char *destination)
  1394. +{
  1395. +    string dir;
  1396. +    copystring(dir, parentdir(destination));
  1397. +    vector<char *> dirs;
  1398. +    while(!fileexists(dir, "r"))
  1399. +    {
  1400. +        dirs.add(newstring(dir));
  1401. +        copystring(dir, parentdir(dir));
  1402. +    }
  1403. +    for(int i = dirs.length() - 1; i >= 0; --i) if(!createdir(dirs[i])) return false;
  1404. +    return true;
  1405. +}
  1406. +
  1407. +#ifndef STANDALONE
  1408. +static int rwopsseek(SDL_RWops *rw, int offset, int whence)
  1409. +{
  1410. +    stream *f = (stream *)rw->hidden.unknown.data1;
  1411. +    if((!offset && whence==SEEK_CUR) || f->seek(offset, whence)) return f->tell();
  1412. +    return -1;
  1413. +}
  1414. +
  1415. +static int rwopsread(SDL_RWops *rw, void *buf, int size, int nmemb)
  1416. +{
  1417. +    stream *f = (stream *)rw->hidden.unknown.data1;
  1418. +    return f->read(buf, size*nmemb)/size;
  1419. +}
  1420. +
  1421. +static int rwopswrite(SDL_RWops *rw, const void *buf, int size, int nmemb)
  1422. +{
  1423. +    stream *f = (stream *)rw->hidden.unknown.data1;
  1424. +    return f->write(buf, size*nmemb)/size;
  1425. +}
  1426. +
  1427. +static int rwopsclose(SDL_RWops *rw)
  1428. +{
  1429. +    return 0;
  1430. +}
  1431. +
  1432. +SDL_RWops *stream::rwops()
  1433. +{
  1434. +    SDL_RWops *rw = SDL_AllocRW();
  1435. +    if(!rw) return NULL;
  1436. +    rw->hidden.unknown.data1 = this;
  1437. +    rw->seek = rwopsseek;
  1438. +    rw->read = rwopsread;
  1439. +    rw->write = rwopswrite;
  1440. +    rw->close = rwopsclose;
  1441. +    return rw;
  1442. +}
  1443. +#endif
  1444. +
  1445. +long stream::size()
  1446. +{
  1447. +    long pos = tell(), endpos;
  1448. +    if(pos < 0 || !seek(0, SEEK_END)) return -1;
  1449. +    endpos = tell();
  1450. +    return pos == endpos || seek(pos, SEEK_SET) ? endpos : -1;
  1451. +}
  1452. +
  1453. +bool stream::getline(char *str, int len)
  1454. +{
  1455. +    loopi(len-1)
  1456. +    {
  1457. +        if(read(&str[i], 1) != 1) { str[i] = '\0'; return i > 0; }
  1458. +        else if(str[i] == '\n') { str[i+1] = '\0'; return true; }
  1459. +    }
  1460. +    if(len > 0) str[len-1] = '\0';
  1461. +    return true;
  1462. +}
  1463. +
  1464. +#ifdef __linux__
  1465. +#include <sys/statvfs.h>
  1466. +#define MINFSSIZE 50000000ull           // 50MB
  1467. +#endif
  1468. +
  1469. +struct filestream : stream
  1470. +{
  1471. +    FILE *file;
  1472. +
  1473. +    filestream() : file(NULL) {}
  1474. +    ~filestream() { close(); }
  1475. +
  1476. +    bool open(const char *name, const char *mode)
  1477. +    {
  1478. +        if(file) return false;
  1479. +        file = fopen(name, mode);
  1480. +#ifdef __linux__
  1481. +        struct statvfs buf;
  1482. +        if(file && strchr(mode,'w'))
  1483. +        {
  1484. +            int fail = fstatvfs(fileno(file), &buf);
  1485. +            if (fail || (unsigned long long)buf.f_frsize * (unsigned long long)buf.f_bavail < MINFSSIZE)
  1486. +            {
  1487. +                close();
  1488. +                return false;
  1489. +            }
  1490. +        }
  1491. +#endif
  1492. +        return file!=NULL;
  1493. +    }
  1494. +
  1495. +    bool opentemp(const char *name, const char *mode)
  1496. +    {
  1497. +        if(file) return false;
  1498. +#ifdef WIN32
  1499. +        file = fopen(name, mode);
  1500. +#else
  1501. +        file = tmpfile();
  1502. +#endif
  1503. +        return file!=NULL;
  1504. +    }
  1505. +
  1506. +    void close()
  1507. +    {
  1508. +        if(file) { fclose(file); file = NULL; }
  1509. +    }
  1510. +
  1511. +    bool end() { return feof(file)!=0; }
  1512. +    long tell() { return ftell(file); }
  1513. +    bool seek(long offset, int whence) { return fseek(file, offset, whence) >= 0; }
  1514. +    int read(void *buf, int len) { return (int)fread(buf, 1, len, file); }
  1515. +    int write(const void *buf, int len) { return (int)fwrite(buf, 1, len, file); }
  1516. +    int getchar() { return fgetc(file); }
  1517. +    bool putchar(int c) { return fputc(c, file)!=EOF; }
  1518. +    bool getline(char *str, int len) { return fgets(str, len, file)!=NULL; }
  1519. +    bool putstring(const char *str) { return fputs(str, file)!=EOF; }
  1520. +
  1521. +    int printf(const char *fmt, ...)
  1522. +    {
  1523. +        va_list v;
  1524. +        va_start(v, fmt);
  1525. +        int result = vfprintf(file, fmt, v);
  1526. +        va_end(v);
  1527. +        return result;
  1528. +    }
  1529. +};
  1530. +
  1531. +#ifndef STANDALONE
  1532. +VAR(dbggz, 0, 0, 1);
  1533. +#endif
  1534. +
  1535. +struct gzstream : stream
  1536. +{
  1537. +    enum
  1538. +    {
  1539. +        MAGIC1   = 0x1F,
  1540. +        MAGIC2   = 0x8B,
  1541. +        BUFSIZE  = 16384,
  1542. +        OS_UNIX  = 0x03
  1543. +    };
  1544. +
  1545. +    enum
  1546. +    {
  1547. +        F_ASCII    = 0x01,
  1548. +        F_CRC      = 0x02,
  1549. +        F_EXTRA    = 0x04,
  1550. +        F_NAME     = 0x08,
  1551. +        F_COMMENT  = 0x10,
  1552. +        F_RESERVED = 0xE0
  1553. +    };
  1554. +
  1555. +    stream *file;
  1556. +    z_stream zfile;
  1557. +    uchar *buf;
  1558. +    bool reading, writing, autoclose;
  1559. +    uint crc;
  1560. +    int headersize;
  1561. +
  1562. +    gzstream() : file(NULL), buf(NULL), reading(false), writing(false), autoclose(false), crc(0), headersize(0)
  1563. +    {
  1564. +        zfile.zalloc = NULL;
  1565. +        zfile.zfree = NULL;
  1566. +        zfile.opaque = NULL;
  1567. +        zfile.next_in = zfile.next_out = NULL;
  1568. +        zfile.avail_in = zfile.avail_out = 0;
  1569. +    }
  1570. +
  1571. +    ~gzstream()
  1572. +    {
  1573. +        close();
  1574. +    }
  1575. +
  1576. +    void writeheader()
  1577. +    {
  1578. +        uchar header[] = { MAGIC1, MAGIC2, Z_DEFLATED, 0, 0, 0, 0, 0, 0, OS_UNIX };
  1579. +        file->write(header, sizeof(header));
  1580. +    }
  1581. +
  1582. +    void readbuf(int size = BUFSIZE)
  1583. +    {
  1584. +        if(!zfile.avail_in) zfile.next_in = (Bytef *)buf;
  1585. +        size = min(size, int(&buf[BUFSIZE] - &zfile.next_in[zfile.avail_in]));
  1586. +        int n = file->read(zfile.next_in + zfile.avail_in, size);
  1587. +        if(n > 0) zfile.avail_in += n;
  1588. +    }
  1589. +
  1590. +    int readbyte(int size = BUFSIZE)
  1591. +    {
  1592. +        if(!zfile.avail_in) readbuf(size);
  1593. +        if(!zfile.avail_in) return 0;
  1594. +        zfile.avail_in--;
  1595. +        return *(uchar *)zfile.next_in++;
  1596. +    }
  1597. +
  1598. +    void skipbytes(int n)
  1599. +    {
  1600. +        while(n > 0 && zfile.avail_in > 0)
  1601. +        {
  1602. +            int skipped = min(n, (int)zfile.avail_in);
  1603. +            zfile.avail_in -= skipped;
  1604. +            zfile.next_in += skipped;
  1605. +            n -= skipped;
  1606. +        }
  1607. +        if(n <= 0) return;
  1608. +        file->seek(n, SEEK_CUR);
  1609. +    }
  1610. +
  1611. +    bool checkheader()
  1612. +    {
  1613. +        readbuf(10);
  1614. +        if(readbyte() != MAGIC1 || readbyte() != MAGIC2 || readbyte() != Z_DEFLATED) return false;
  1615. +        int flags = readbyte();
  1616. +        if(flags & F_RESERVED) return false;
  1617. +        skipbytes(6);
  1618. +        if(flags & F_EXTRA)
  1619. +        {
  1620. +            int len = readbyte(512);
  1621. +            len |= readbyte(512)<<8;
  1622. +            skipbytes(len);
  1623. +        }
  1624. +        if(flags & F_NAME) while(readbyte(512));
  1625. +        if(flags & F_COMMENT) while(readbyte(512));
  1626. +        if(flags & F_CRC) skipbytes(2);
  1627. +        headersize = file->tell() - zfile.avail_in;
  1628. +        return zfile.avail_in > 0 || !file->end();
  1629. +    }
  1630. +
  1631. +    bool open(stream *f, const char *mode, bool needclose, int level)
  1632. +    {
  1633. +        if(file) return false;
  1634. +        for(; *mode; mode++)
  1635. +        {
  1636. +            if(*mode=='r') { reading = true; break; }
  1637. +            else if(*mode=='w') { writing = true; break; }
  1638. +        }
  1639. +        if(reading)
  1640. +        {
  1641. +            if(inflateInit2(&zfile, -MAX_WBITS) != Z_OK) reading = false;
  1642. +        }
  1643. +        else if(writing && deflateInit2(&zfile, level, Z_DEFLATED, -MAX_WBITS, min(MAX_MEM_LEVEL, 8), Z_DEFAULT_STRATEGY) != Z_OK) writing = false;
  1644. +        if(!reading && !writing) return false;
  1645. +
  1646. +        autoclose = needclose;
  1647. +        file = f;
  1648. +        crc = crc32(0, NULL, 0);
  1649. +        buf = new uchar[BUFSIZE];
  1650. +
  1651. +        if(reading)
  1652. +        {
  1653. +            if(!checkheader()) { stopreading(); return false; }
  1654. +        }
  1655. +        else if(writing) writeheader();
  1656. +        return true;
  1657. +    }
  1658. +
  1659. +    uint getcrc() { return crc; }
  1660. +
  1661. +    void finishreading()
  1662. +    {
  1663. +        if(!reading) return;
  1664. +#ifndef STANDALONE
  1665. +        if(dbggz)
  1666. +        {
  1667. +            uint checkcrc = 0, checksize = 0;
  1668. +            loopi(4) checkcrc |= uint(readbyte()) << (i*8);
  1669. +            loopi(4) checksize |= uint(readbyte()) << (i*8);
  1670. +            if(checkcrc != crc)
  1671. +                conoutf("gzip crc check failed: read %X, calculated %X", checkcrc, crc);
  1672. +            if(checksize != zfile.total_out)
  1673. +                conoutf("gzip size check failed: read %d, calculated %d", checksize, zfile.total_out);
  1674. +        }
  1675. +#endif
  1676. +    }
  1677. +
  1678. +    void stopreading()
  1679. +    {
  1680. +        if(!reading) return;
  1681. +        inflateEnd(&zfile);
  1682. +        reading = false;
  1683. +    }
  1684. +
  1685. +    void finishwriting()
  1686. +    {
  1687. +        if(!writing) return;
  1688. +        for(;;)
  1689. +        {
  1690. +            int err = zfile.avail_out > 0 ? deflate(&zfile, Z_FINISH) : Z_OK;
  1691. +            if(err != Z_OK && err != Z_STREAM_END) break;
  1692. +            flush();
  1693. +            if(err == Z_STREAM_END) break;
  1694. +        }
  1695. +        uchar trailer[8] =
  1696. +        {
  1697. +            crc&0xFF, (crc>>8)&0xFF, (crc>>16)&0xFF, (crc>>24)&0xFF,
  1698. +            zfile.total_in&0xFF, (zfile.total_in>>8)&0xFF, (zfile.total_in>>16)&0xFF, (zfile.total_in>>24)&0xFF
  1699. +        };
  1700. +        file->write(trailer, sizeof(trailer));
  1701. +    }
  1702. +
  1703. +    void stopwriting()
  1704. +    {
  1705. +        if(!writing) return;
  1706. +        deflateEnd(&zfile);
  1707. +        writing = false;
  1708. +    }
  1709. +
  1710. +    void close()
  1711. +    {
  1712. +        if(reading) finishreading();
  1713. +        stopreading();
  1714. +        if(writing) finishwriting();
  1715. +        stopwriting();
  1716. +        DELETEA(buf);
  1717. +        if(autoclose) DELETEP(file);
  1718. +    }
  1719. +
  1720. +    bool end() { return !reading && !writing; }
  1721. +    long tell() { return reading ? zfile.total_out : (writing ? zfile.total_in : -1); }
  1722. +
  1723. +    bool seek(long offset, int whence)
  1724. +    {
  1725. +        if(writing || !reading) return false;
  1726. +
  1727. +        if(whence == SEEK_END)
  1728. +        {
  1729. +            uchar skip[512];
  1730. +            while(read(skip, sizeof(skip)) == sizeof(skip));
  1731. +            return !offset;
  1732. +        }
  1733. +        else if(whence == SEEK_CUR) offset += zfile.total_out;
  1734. +
  1735. +        if(offset >= (int)zfile.total_out) offset -= zfile.total_out;
  1736. +        else if(offset < 0 || !file->seek(headersize, SEEK_SET)) return false;
  1737. +        else
  1738. +        {
  1739. +            if(zfile.next_in && zfile.total_in <= uint(zfile.next_in - buf))
  1740. +            {
  1741. +                zfile.avail_in += zfile.total_in;
  1742. +                zfile.next_in -= zfile.total_in;
  1743. +            }
  1744. +            else
  1745. +            {
  1746. +                zfile.avail_in = 0;
  1747. +                zfile.next_in = NULL;
  1748. +            }
  1749. +            inflateReset(&zfile);
  1750. +            crc = crc32(0, NULL, 0);
  1751. +        }
  1752. +
  1753. +        uchar skip[512];
  1754. +        while(offset > 0)
  1755. +        {
  1756. +            int skipped = min(offset, (long)sizeof(skip));
  1757. +            if(read(skip, skipped) != skipped) { stopreading(); return false; }
  1758. +            offset -= skipped;
  1759. +        }
  1760. +
  1761. +        return true;
  1762. +    }
  1763. +
  1764. +    int read(void *buf, int len)
  1765. +    {
  1766. +        if(!reading || !buf || !len) return 0;
  1767. +        zfile.next_out = (Bytef *)buf;
  1768. +        zfile.avail_out = len;
  1769. +        while(zfile.avail_out > 0)
  1770. +        {
  1771. +            if(!zfile.avail_in)
  1772. +            {
  1773. +                readbuf(BUFSIZE);
  1774. +                if(!zfile.avail_in) { stopreading(); break; }
  1775. +            }
  1776. +            int err = inflate(&zfile, Z_NO_FLUSH);
  1777. +            if(err == Z_STREAM_END) { crc = crc32(crc, (Bytef *)buf, len - zfile.avail_out); finishreading(); stopreading(); return len - zfile.avail_out; }
  1778. +            else if(err != Z_OK) { stopreading(); break; }
  1779. +        }
  1780. +        crc = crc32(crc, (Bytef *)buf, len - zfile.avail_out);
  1781. +        return len - zfile.avail_out;
  1782. +    }
  1783. +
  1784. +    bool flush()
  1785. +    {
  1786. +        if(zfile.next_out && zfile.avail_out < BUFSIZE)
  1787. +        {
  1788. +            if(file->write(buf, BUFSIZE - zfile.avail_out) != int(BUFSIZE - zfile.avail_out))
  1789. +                return false;
  1790. +        }
  1791. +        zfile.next_out = buf;
  1792. +        zfile.avail_out = BUFSIZE;
  1793. +        return true;
  1794. +    }
  1795. +
  1796. +    int write(const void *buf, int len)
  1797. +    {
  1798. +        if(!writing || !buf || !len) return 0;
  1799. +        zfile.next_in = (Bytef *)buf;
  1800. +        zfile.avail_in = len;
  1801. +        while(zfile.avail_in > 0)
  1802. +        {
  1803. +            if(!zfile.avail_out && !flush()) { stopwriting(); break; }
  1804. +            int err = deflate(&zfile, Z_NO_FLUSH);
  1805. +            if(err != Z_OK) { stopwriting(); break; }
  1806. +        }
  1807. +        crc = crc32(crc, (Bytef *)buf, len - zfile.avail_in);
  1808. +        return len - zfile.avail_in;
  1809. +    }
  1810. +};
  1811. +
  1812. +
  1813. +stream *openrawfile(const char *filename, const char *mode)
  1814. +{
  1815. +    const char *found = findfile(filename, mode);
  1816. +#ifndef STANDALONE
  1817. +    if(mode && (mode[0]=='w' || mode[0]=='a')) conoutf("writing to file: %s", found);
  1818. +#endif
  1819. +    if(!found) return NULL;
  1820. +    filestream *file = new filestream;
  1821. +    if(!file->open(found, mode))
  1822. +    {
  1823. +#ifndef STANDALONE
  1824. +//         conoutf("file failure! %s",filename);
  1825. +#endif
  1826. +        delete file; return NULL;
  1827. +    }
  1828. +    return file;
  1829. +}
  1830. +
  1831. +stream *openfile(const char *filename, const char *mode)
  1832. +{
  1833. +#ifndef STANDALONE
  1834. +    stream *s = openzipfile(filename, mode);
  1835. +    if(s) return s;
  1836. +#endif
  1837. +    return openrawfile(filename, mode);
  1838. +}
  1839. +
  1840. +int getfilesize(const char *filename)
  1841. +{
  1842. +    stream *f = openfile(filename, "rb");
  1843. +    if(!f) return -1;
  1844. +    int len = f->size();
  1845. +    delete f;
  1846. +    return len;
  1847. +}
  1848. +
  1849. +stream *opentempfile(const char *name, const char *mode)
  1850. +{
  1851. +    const char *found = findfile(name, mode);
  1852. +    filestream *file = new filestream;
  1853. +    if(!file->opentemp(found ? found : name, mode)) { delete file; return NULL; }
  1854. +    return file;
  1855. +}
  1856. +
  1857. +stream *opengzfile(const char *filename, const char *mode, stream *file, int level)
  1858. +{
  1859. +    stream *source = file ? file : openfile(filename, mode);
  1860. +    if(!source) return NULL;
  1861. +    gzstream *gz = new gzstream;
  1862. +    if(!gz->open(source, mode, !file, level)) { if(!file) delete source; return NULL; }
  1863. +    return gz;
  1864. +}
  1865. +
  1866. +char *loadfile(const char *fn, int *size, const char *mode)
  1867. +{
  1868. +    stream *f = openfile(fn, mode ? mode : "rb");
  1869. +    if(!f) return NULL;
  1870. +    int len = f->size();
  1871. +    if(len<=0) { delete f; return NULL; }
  1872. +    char *buf = new char[len+1];
  1873. +    if(!buf) { delete f; return NULL; }
  1874. +    buf[len] = 0;
  1875. +    int rlen = f->read(buf, len);
  1876. +    delete f;
  1877. +    if(len!=rlen && (!mode || strchr(mode, 'b')))
  1878. +    {
  1879. +        delete[] buf;
  1880. +        return NULL;
  1881. +    }
  1882. +    if(size!=NULL) *size = len;
  1883. +    return buf;
  1884. +}
  1885. +
  1886. Index: tools.h
  1887. ===================================================================
  1888. --- tools.h (revision 6612)
  1889. +++ tools.h (working copy)
  1890. @@ -901,6 +901,8 @@
  1891.  extern int listfiles(const char *dir, const char *ext, vector<char *> &files);
  1892.  extern int listzipfiles(const char *dir, const char *ext, vector<char *> &files);
  1893.  extern bool delfile(const char *path);
  1894. +extern bool copyfile(const char *source, const char *destination);
  1895. +extern bool preparedir(const char *destination);
  1896.  extern struct mapstats *loadmapstats(const char *filename, bool getlayout);
  1897.  extern bool cmpb(void *b, int n, enet_uint32 c);
  1898.  extern bool cmpf(char *fn, enet_uint32 c);
  1899. Index: worldio.cpp
  1900. ===================================================================
  1901. --- worldio.cpp (revision 6612)
  1902. +++ worldio.cpp (working copy)
  1903. @@ -840,6 +840,12 @@
  1904.      delete f;
  1905.  }
  1906.  
  1907. +// get all dependencies for the current map
  1908. +void curmapdeps()
  1909. +{
  1910. +    
  1911. +}
  1912. +
  1913.  COMMAND(listmapdependencies, ARG_1STR);
  1914.  
  1915.  void listmapdependencies_all(int sure)
Add Comment
Please, Sign In to add comment