Line data Source code
1 : /**
2 : * @file stream.cpp
3 : * @brief utilities for character streams
4 : *
5 : * stream.cpp defines character handling to enable character streams to be written
6 : * into and out of files
7 : * also included is utilities for gz archive support
8 : */
9 : #include <sstream>
10 :
11 : #include <cstring>
12 : #include <functional>
13 : #include <queue>
14 : #include <stack>
15 : #include <array>
16 : #include <algorithm>
17 :
18 : #include <SDL.h>
19 :
20 : #include <GL/glew.h>
21 :
22 : #include <GL/gl.h>
23 :
24 : #include <zlib.h>
25 :
26 : #include "../libprimis-headers/tools.h"
27 : #include "../libprimis-headers/command.h"
28 : #include "../libprimis-headers/consts.h"
29 :
30 : #include "stream.h"
31 :
32 : #include "../engine/interface/console.h"
33 :
34 : ///////////////////////// character conversion /////////////////////////////////
35 :
36 : #define CUBECTYPE(s, p, d, a, A, u, U) \
37 : 0, U, U, U, U, U, U, U, U, s, s, s, s, s, U, U, \
38 : U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, \
39 : s, p, p, p, p, p, p, p, p, p, p, p, p, p, p, p, \
40 : d, d, d, d, d, d, d, d, d, d, p, p, p, p, p, p, \
41 : p, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, \
42 : A, A, A, A, A, A, A, A, A, A, A, p, p, p, p, p, \
43 : p, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, \
44 : a, a, a, a, a, a, a, a, a, a, a, p, p, p, p, U, \
45 : U, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, \
46 : u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, U, \
47 : u, U, u, U, u, U, u, U, u, U, u, U, u, U, u, U, \
48 : u, U, u, U, u, U, u, U, u, U, u, U, u, U, u, U, \
49 : u, U, u, U, u, U, u, U, U, u, U, u, U, u, U, U, \
50 : U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, \
51 : U, U, U, U, u, u, u, u, u, u, u, u, u, u, u, u, \
52 : u, u, u, u, u, u, u, u, u, u, u, u, u, u, U, u
53 :
54 : /* note here:
55 : * these vars are declared extern inline to allow a `const` (implicitly also
56 : * `static`) to be linked to other files as a `const`.
57 : *
58 : * these vars cannot be `constexpr` due to it not being legal to define constexpr
59 : * prototypes in headers
60 : */
61 :
62 : extern const uchar cubectype[256] =
63 : {
64 : CUBECTYPE(CubeType_Space,
65 : CubeType_Print,
66 : CubeType_Print | CubeType_Digit,
67 : CubeType_Print | CubeType_Alpha | CubeType_Lower,
68 : CubeType_Print | CubeType_Alpha | CubeType_Upper,
69 : CubeType_Print | CubeType_Unicode | CubeType_Alpha | CubeType_Lower,
70 : CubeType_Print | CubeType_Unicode | CubeType_Alpha | CubeType_Upper)
71 : };
72 :
73 696 : size_t encodeutf8(uchar *dstbuf, size_t dstlen, const uchar *srcbuf, size_t srclen, size_t *carry)
74 : {
75 696 : uchar *dst = dstbuf,
76 696 : *dstend = &dstbuf[dstlen];
77 696 : const uchar *src = srcbuf,
78 696 : *srcend = &srcbuf[srclen];
79 696 : if(src < srcend && dst < dstend)
80 : {
81 : do
82 : {
83 696 : int uni = cube2uni(*src);
84 696 : if(uni <= 0x7F)
85 : {
86 696 : if(dst >= dstend)
87 : {
88 0 : goto done;
89 : }
90 696 : const uchar *end = std::min<const uchar *>(srcend, &src[dstend-dst]);
91 : do
92 : {
93 13134 : if(uni == '\f')
94 : {
95 0 : if(++src >= srcend)
96 : {
97 0 : goto done;
98 : }
99 0 : goto uni1;
100 : }
101 13134 : *dst++ = uni;
102 13134 : if(++src >= end)
103 : {
104 696 : goto done;
105 : }
106 12438 : uni = cube2uni(*src);
107 12438 : } while(uni <= 0x7F);
108 : }
109 0 : if(uni <= 0x7FF)
110 : {
111 0 : if(dst + 2 > dstend)
112 : {
113 0 : goto done;
114 : }
115 0 : *dst++ = 0xC0 | (uni>>6);
116 0 : goto uni2;
117 : }
118 0 : else if(uni <= 0xFFFF)
119 : {
120 0 : if(dst + 3 > dstend)
121 : {
122 0 : goto done;
123 : }
124 0 : *dst++ = 0xE0 | (uni>>12);
125 0 : goto uni3;
126 : }
127 0 : else if(uni <= 0x1FFFFF)
128 : {
129 0 : if(dst + 4 > dstend)
130 : {
131 0 : goto done;
132 : }
133 0 : *dst++ = 0xF0 | (uni>>18);
134 0 : goto uni4;
135 : }
136 0 : else if(uni <= 0x3FFFFFF)
137 : {
138 0 : if(dst + 5 > dstend)
139 : {
140 0 : goto done;
141 : }
142 0 : *dst++ = 0xF8 | (uni>>24);
143 0 : goto uni5;
144 : }
145 : else if(uni <= 0x7FFFFFFF)
146 : {
147 0 : if(dst + 6 > dstend)
148 : {
149 0 : goto done;
150 : }
151 0 : *dst++ = 0xFC | (uni>>30);
152 0 : goto uni6;
153 : }
154 : else
155 : {
156 : goto uni1;
157 : }
158 0 : uni6: *dst++ = 0x80 | ((uni>>24)&0x3F);
159 0 : uni5: *dst++ = 0x80 | ((uni>>18)&0x3F);
160 0 : uni4: *dst++ = 0x80 | ((uni>>12)&0x3F);
161 0 : uni3: *dst++ = 0x80 | ((uni>>6)&0x3F);
162 0 : uni2: *dst++ = 0x80 | (uni&0x3F);
163 0 : uni1:;
164 0 : } while(++src < srcend);
165 : }
166 0 : done:
167 696 : if(carry)
168 : {
169 696 : *carry += src - srcbuf;
170 : }
171 696 : return dst - dstbuf;
172 : }
173 :
174 : ///////////////////////// file system ///////////////////////
175 :
176 : #ifdef WIN32
177 : #include <shlobj.h>
178 : #else
179 : #include <unistd.h>
180 : #include <sys/stat.h>
181 : #include <sys/types.h>
182 : #include <dirent.h>
183 : #endif
184 :
185 : std::string homedir = "";
186 : struct packagedir
187 : {
188 : std::string dir;
189 : std::string filter;
190 : };
191 : static std::vector<packagedir> packagedirs;
192 :
193 12 : char *makerelpath(const char *dir, const char *file, const char *prefix, const char *cmd)
194 : {
195 : static string tmp;
196 12 : if(prefix)
197 : {
198 0 : copystring(tmp, prefix);
199 : }
200 : else
201 : {
202 12 : tmp[0] = '\0';
203 : }
204 12 : if(file[0]=='<')
205 : {
206 0 : const char *end = std::strrchr(file, '>');
207 0 : if(end)
208 : {
209 0 : size_t len = std::strlen(tmp);
210 0 : copystring(&tmp[len], file, std::min<size_t>(sizeof(tmp)-len, static_cast<size_t>(end+2-file)));
211 0 : file = end+1;
212 : }
213 : }
214 12 : if(cmd)
215 : {
216 0 : concatstring(tmp, cmd);
217 : }
218 12 : if(dir)
219 : {
220 12 : DEF_FORMAT_STRING(pname, "%s/%s", dir, file);
221 12 : concatstring(tmp, pname);
222 : }
223 : else
224 : {
225 0 : concatstring(tmp, file);
226 : }
227 12 : return tmp;
228 : }
229 :
230 29 : char *path(char *s)
231 : {
232 29 : for(char *curpart = s;;)
233 : {
234 33 : char *endpart = std::strchr(curpart, '&');
235 33 : if(endpart)
236 : {
237 4 : *endpart = '\0';
238 : }
239 33 : if(curpart[0]=='<')
240 : {
241 4 : char *file = std::strrchr(curpart, '>');
242 4 : if(!file)
243 : {
244 0 : return s;
245 : }
246 4 : curpart = file+1;
247 : }
248 111 : for(char *t = curpart; (t = std::strpbrk(t, "/\\")); *t++ = PATHDIV)
249 : {
250 : //(empty body)
251 : }
252 33 : for(char *prevdir = nullptr, *curdir = curpart;;)
253 : {
254 104 : prevdir = curdir[0]==PATHDIV ? curdir+1 : curdir;
255 104 : curdir = std::strchr(prevdir, PATHDIV);
256 104 : if(!curdir)
257 : {
258 33 : break;
259 : }
260 71 : if(prevdir+1==curdir && prevdir[0]=='.')
261 : {
262 4 : std::memmove(prevdir, curdir+1, std::strlen(curdir+1)+1);
263 4 : curdir = prevdir;
264 : }
265 67 : else if(curdir[1]=='.' && curdir[2]=='.' && curdir[3]==PATHDIV)
266 : {
267 8 : if(prevdir+2==curdir && prevdir[0]=='.' && prevdir[1]=='.')
268 : {
269 0 : continue;
270 : }
271 8 : std::memmove(prevdir, curdir+4, std::strlen(curdir+4)+1);
272 8 : if(prevdir-2 >= curpart && prevdir[-1]==PATHDIV)
273 : {
274 1 : prevdir -= 2;
275 2 : while(prevdir-1 >= curpart && prevdir[-1] != PATHDIV)
276 : {
277 1 : --prevdir;
278 : }
279 : }
280 8 : curdir = prevdir;
281 : }
282 : }
283 33 : if(endpart)
284 : {
285 4 : *endpart = '&';
286 4 : curpart = endpart+1;
287 : }
288 : else
289 : {
290 29 : break;
291 : }
292 4 : }
293 29 : return s;
294 : }
295 :
296 58 : std::string path(std::string s)
297 : {
298 58 : std::string truncated_path, processed_path;
299 :
300 58 : size_t path_begin = 0,
301 58 : path_end = s.length();
302 :
303 : // Find the in-line command segment and skip it
304 58 : if(s.find('<') != std::string::npos)
305 : {
306 4 : size_t command_end = s.rfind('>');
307 4 : if(command_end != std::string::npos)
308 : {
309 4 : path_begin = command_end + 1;
310 : }
311 : }
312 :
313 : // Find the conjugated path and cut it
314 58 : if(s.find('&') != std::string::npos)
315 : {
316 4 : path_end = s.find('&');
317 : }
318 :
319 58 : truncated_path = s.substr(path_begin, path_end - path_begin);
320 :
321 : // Handle "."" and ".."" in the path
322 58 : std::istringstream path_stream(truncated_path);
323 58 : std::stack<std::string> path_stack;
324 58 : std::string token;
325 :
326 : // Construct a stack of path tokens
327 271 : while(std::getline(path_stream, token, '/'))
328 : {
329 213 : if(token == "..")
330 : {
331 8 : if(!path_stack.empty())
332 : {
333 6 : path_stack.pop();
334 : }
335 : else
336 : {
337 2 : path_stack.push(token);
338 : }
339 : }
340 205 : else if(!token.empty() && token != ".")
341 : {
342 198 : path_stack.push(token);
343 : }
344 : }
345 :
346 : // Re-construct the processed path from the stack
347 252 : while(!path_stack.empty())
348 : {
349 194 : if(path_stack.size() > 1)
350 : {
351 142 : processed_path = "/" + path_stack.top() + processed_path;
352 : }
353 : else
354 : {
355 52 : processed_path = path_stack.top() + processed_path;
356 : }
357 194 : path_stack.pop();
358 : }
359 :
360 116 : return processed_path;
361 58 : }
362 :
363 2 : char *copypath(const char *s)
364 : {
365 : static string tmp;
366 2 : copystring(tmp, s);
367 2 : path(tmp);
368 2 : return tmp;
369 : }
370 :
371 7 : const char *parentdir(const char *directory)
372 : {
373 7 : const char *p = directory + std::strlen(directory);
374 48 : while(p > directory && *p != '/' && *p != '\\')
375 : {
376 41 : p--;
377 : }
378 : static string parent;
379 7 : size_t len = p-directory+1;
380 7 : copystring(parent, directory, len);
381 7 : return parent;
382 : }
383 :
384 2 : bool fileexists(const char *path, const char *mode)
385 : {
386 2 : bool exists = true;
387 2 : if(mode[0]=='w' || mode[0]=='a')
388 : {
389 1 : path = parentdir(path);
390 : }
391 : #ifdef WIN32
392 : if(GetFileAttributes(path[0] ? path : ".\\") == INVALID_FILE_ATTRIBUTES)
393 : {
394 : exists = false;
395 : }
396 : #else
397 2 : if(access(path[0] ? path : ".", mode[0]=='w' || mode[0]=='a' ? W_OK : (mode[0]=='d' ? X_OK : R_OK)) == -1)
398 : {
399 1 : exists = false;
400 : }
401 : #endif
402 2 : return exists;
403 : }
404 :
405 1 : bool createdir(const char *path)
406 : {
407 1 : size_t len = std::strlen(path);
408 1 : if(path[len-1] == PATHDIV)
409 : {
410 : static string strip;
411 1 : path = copystring(strip, path, len);
412 : }
413 : #ifdef WIN32
414 : return CreateDirectory(path, nullptr) != 0;
415 : #else
416 1 : return mkdir(path, 0777) == 0;
417 : #endif
418 : }
419 :
420 3 : size_t fixpackagedir(char *dir)
421 : {
422 3 : path(dir);
423 3 : size_t len = std::strlen(dir);
424 3 : if(len > 0 && dir[len-1] != PATHDIV)
425 : {
426 1 : dir[len] = PATHDIV;
427 1 : dir[len+1] = '\0';
428 : }
429 3 : return len;
430 : }
431 :
432 0 : bool subhomedir(char *dst, int len, const char *src)
433 : {
434 0 : const char *sub = std::strstr(src, "$HOME");
435 0 : if(!sub)
436 : {
437 0 : sub = std::strchr(src, '~');
438 : }
439 0 : if(sub && sub-src < len)
440 : {
441 : #ifdef WIN32
442 : char home[MAX_PATH+1];
443 : home[0] = '\0';
444 : if(SHGetFolderPath(nullptr, CSIDL_PERSONAL, nullptr, 0, home) != S_OK || !home[0])
445 : {
446 : return false;
447 : }
448 : #else
449 0 : const char *home = getenv("HOME");
450 0 : if(!home || !home[0])
451 : {
452 0 : return false;
453 : }
454 : #endif
455 0 : dst[sub-src] = '\0';
456 0 : concatstring(dst, home, len);
457 0 : concatstring(dst, sub+(*sub == '~' ? 1 : std::strlen("$HOME")), len);
458 : }
459 0 : return true;
460 : }
461 :
462 0 : const char *sethomedir(const char *dir)
463 : {
464 : string pdir;
465 0 : copystring(pdir, dir);
466 0 : if(!subhomedir(pdir, sizeof(pdir), dir) || !fixpackagedir(pdir))
467 : {
468 0 : return nullptr;
469 : }
470 0 : homedir = pdir;
471 0 : return homedir.c_str();
472 : }
473 :
474 0 : const char *addpackagedir(const char *dir)
475 : {
476 : string pdir;
477 0 : copystring(pdir, dir);
478 0 : if(!subhomedir(pdir, sizeof(pdir), dir) || !fixpackagedir(pdir))
479 : {
480 0 : return nullptr;
481 : }
482 0 : char *filter = pdir;
483 : for(;;)
484 : {
485 : static int len = std::strlen("media");
486 0 : filter = std::strstr(filter, "media");
487 0 : if(!filter)
488 : {
489 0 : break;
490 : }
491 0 : if(filter > pdir && filter[-1] == PATHDIV && filter[len] == PATHDIV)
492 : {
493 0 : break;
494 : }
495 0 : filter += len;
496 0 : }
497 0 : packagedir pf;
498 0 : pf.dir = filter ? std::string(pdir, filter-pdir) : std::string(pdir);
499 0 : pf.filter = filter ? std::string(filter) : "";
500 0 : packagedirs.push_back(pf);
501 0 : return pf.dir.c_str();
502 0 : }
503 :
504 22 : const char *findfile(const char *filename, const char *mode)
505 : {
506 : static string s;
507 22 : if(homedir[0])
508 : {
509 0 : formatstring(s, "%s%s", homedir.c_str(), filename);
510 0 : if(fileexists(s, mode))
511 : {
512 0 : return s;
513 : }
514 0 : if(mode[0] == 'w' || mode[0] == 'a')
515 : {
516 : string dirs;
517 0 : copystring(dirs, s);
518 0 : char *dir = std::strchr(dirs[0] == PATHDIV ? dirs+1 : dirs, PATHDIV);
519 0 : while(dir)
520 : {
521 0 : *dir = '\0';
522 0 : if(!fileexists(dirs, "d") && !createdir(dirs))
523 : {
524 0 : return s;
525 : }
526 0 : *dir = PATHDIV;
527 0 : dir = std::strchr(dir+1, PATHDIV);
528 : }
529 0 : return s;
530 : }
531 : }
532 22 : if(mode[0] == 'w' || mode[0] == 'a')
533 : {
534 1 : return filename;
535 : }
536 21 : for(const packagedir &pf : packagedirs)
537 : {
538 0 : if(pf.filter.size() > 0 && std::strncmp(filename, pf.filter.c_str(), pf.filter.size()))
539 : {
540 0 : continue;
541 : }
542 0 : formatstring(s, "%s%s", pf.dir.c_str(), filename);
543 0 : if(fileexists(s, mode))
544 : {
545 0 : return s;
546 : }
547 : }
548 21 : if(mode[0]=='e')
549 : {
550 0 : return nullptr;
551 : }
552 21 : return filename;
553 : }
554 :
555 1 : bool listdir(const char *dirname, bool rel, const char *ext, std::vector<char *> &files)
556 : {
557 1 : size_t extsize = ext ? std::strlen(ext)+1 : 0;
558 : #ifdef WIN32
559 : DEF_FORMAT_STRING(pathname, rel ? ".\\%s\\*.%s" : "%s\\*.%s", dirname, ext ? ext : "*");
560 : WIN32_FIND_DATA FindFileData;
561 : HANDLE Find = FindFirstFile(pathname, &FindFileData);
562 : if(Find != INVALID_HANDLE_VALUE)
563 : {
564 : do {
565 : if(!ext)
566 : {
567 : files.push_back(newstring(FindFileData.cFileName));
568 : }
569 : else
570 : {
571 : size_t namelen = std::strlen(FindFileData.cFileName);
572 : if(namelen > extsize)
573 : {
574 : namelen -= extsize;
575 : if(FindFileData.cFileName[namelen] == '.' && std::strncmp(FindFileData.cFileName+namelen+1, ext, extsize-1)==0)
576 : {
577 : files.push_back(newstring(FindFileData.cFileName, namelen));
578 : }
579 : }
580 : }
581 : } while(FindNextFile(Find, &FindFileData));
582 : FindClose(Find);
583 : return true;
584 : }
585 : #else
586 1 : DEF_FORMAT_STRING(pathname, rel ? "./%s" : "%s", dirname);
587 1 : DIR *d = opendir(pathname);
588 1 : if(d)
589 : {
590 : struct dirent *de;
591 82 : while((de = readdir(d)) != nullptr)
592 : {
593 81 : if(!ext)
594 : {
595 81 : files.push_back(newstring(de->d_name));
596 : }
597 : else
598 : {
599 0 : size_t namelen = std::strlen(de->d_name);
600 0 : if(namelen > extsize)
601 : {
602 0 : namelen -= extsize;
603 0 : if(de->d_name[namelen] == '.' && std::strncmp(de->d_name+namelen+1, ext, extsize-1)==0)
604 : {
605 0 : files.push_back(newstring(de->d_name, namelen));
606 : }
607 : }
608 : }
609 : }
610 1 : closedir(d);
611 1 : return true;
612 : }
613 : #endif
614 : else
615 : {
616 0 : return false;
617 : }
618 : }
619 :
620 1 : int listfiles(const char *dir, const char *ext, std::vector<char *> &files)
621 : {
622 : string dirname;
623 1 : copystring(dirname, dir);
624 1 : path(dirname);
625 1 : size_t dirlen = std::strlen(dirname);
626 1 : while(dirlen > 1 && dirname[dirlen-1] == PATHDIV)
627 : {
628 0 : dirname[--dirlen] = '\0';
629 : }
630 1 : int dirs = 0;
631 1 : if(listdir(dirname, true, ext, files))
632 : {
633 1 : dirs++;
634 : }
635 : string s;
636 1 : if(homedir[0])
637 : {
638 0 : formatstring(s, "%s%s", homedir.c_str(), dirname);
639 0 : if(listdir(s, false, ext, files))
640 : {
641 0 : dirs++;
642 : }
643 : }
644 1 : for(const packagedir &pf : packagedirs)
645 : {
646 0 : if(pf.filter.size() && std::strncmp(dirname, pf.filter.c_str(), dirlen == pf.filter.size()-1 ? dirlen : pf.filter.size()))
647 : {
648 0 : continue;
649 : }
650 0 : formatstring(s, "%s%s", pf.dir.c_str(), dirname);
651 0 : if(listdir(s, false, ext, files))
652 : {
653 0 : dirs++;
654 : }
655 : }
656 1 : dirs += listzipfiles(dirname, ext, files);
657 1 : return dirs;
658 : }
659 :
660 0 : static Sint64 rwopsseek(SDL_RWops *rw, Sint64 pos, int whence)
661 : {
662 0 : stream *f = static_cast<stream *>(rw->hidden.unknown.data1);
663 0 : if((!pos && whence==SEEK_CUR) || f->seek(pos, whence))
664 : {
665 0 : return static_cast<int>(f->tell());
666 : }
667 0 : return -1;
668 : }
669 :
670 0 : static size_t rwopsread(SDL_RWops *rw, void *buf, size_t size, size_t nmemb)
671 : {
672 0 : stream *f = static_cast<stream *>(rw->hidden.unknown.data1);
673 0 : return f->read(buf, size*nmemb)/size;
674 : }
675 :
676 0 : static size_t rwopswrite(SDL_RWops *rw, const void *buf, size_t size, size_t nmemb)
677 : {
678 0 : stream *f = static_cast<stream *>(rw->hidden.unknown.data1);
679 0 : return f->write(buf, size*nmemb)/size;
680 : }
681 :
682 1 : SDL_RWops *stream::rwops()
683 : {
684 1 : SDL_RWops *rw = SDL_AllocRW();
685 1 : if(!rw)
686 : {
687 0 : return nullptr;
688 : }
689 1 : rw->hidden.unknown.data1 = this;
690 1 : rw->seek = rwopsseek;
691 1 : rw->read = rwopsread;
692 1 : rw->write = rwopswrite;
693 1 : rw->close = 0;
694 1 : return rw;
695 : }
696 :
697 2 : stream::offset stream::size()
698 : {
699 2 : offset pos = tell(),
700 : endpos;
701 2 : if(pos < 0 || !seek(0, SEEK_END))
702 : {
703 2 : return -1;
704 : }
705 0 : endpos = tell();
706 0 : return pos == endpos || seek(pos, SEEK_SET) ? endpos : -1;
707 : }
708 :
709 1 : bool stream::getline(char *str, size_t len)
710 : {
711 1 : for(int i = 0; i < static_cast<int>(len-1); ++i)
712 : {
713 0 : if(read(&str[i], 1) != 1)
714 : {
715 0 : str[i] = '\0';
716 0 : return i > 0;
717 : }
718 0 : else if(str[i] == '\n')
719 : {
720 0 : str[i+1] = '\0';
721 0 : return true;
722 : }
723 : }
724 1 : if(len > 0)
725 : {
726 0 : str[len-1] = '\0';
727 : }
728 1 : return true;
729 : }
730 :
731 1 : size_t stream::printf(const char *fmt, ...)
732 : {
733 : char buf[512];
734 1 : char *str = buf;
735 : va_list args;
736 : #if defined(WIN32) && !defined(__GNUC__)
737 : va_start(args, fmt);
738 : int len = _vscprintf(fmt, args);
739 : if(len <= 0)
740 : {
741 : va_end(args);
742 : return 0;
743 : }
744 : if(len >= static_cast<int>(sizeof(buf)))
745 : {
746 : str = new char[len+1];
747 : }
748 : _vsnprintf(str, len+1, fmt, args);
749 : va_end(args);
750 : #else
751 1 : va_start(args, fmt);
752 1 : int len = vsnprintf(buf, sizeof(buf), fmt, args);
753 1 : va_end(args);
754 1 : if(len <= 0)
755 : {
756 0 : return 0;
757 : }
758 1 : if(len >= static_cast<int>(sizeof(buf)))
759 : {
760 0 : str = new char[len+1];
761 0 : va_start(args, fmt);
762 0 : vsnprintf(str, len+1, fmt, args);
763 0 : va_end(args);
764 : }
765 : #endif
766 1 : size_t n = write(str, len);
767 1 : if(str != buf)
768 : {
769 0 : delete[] str;
770 : }
771 1 : return n;
772 : }
773 :
774 : struct filestream final : stream
775 : {
776 : FILE *file;
777 :
778 11 : filestream() : file(nullptr) {}
779 22 : ~filestream() { close(); }
780 :
781 11 : bool open(const char *name, const char *mode)
782 : {
783 11 : if(file)
784 : {
785 0 : return false;
786 : }
787 11 : file = fopen(name, mode);
788 11 : return file!=nullptr;
789 : }
790 : #ifdef WIN32
791 : bool opentemp(const char *name, const char *mode)
792 : {
793 : if(file)
794 : {
795 : return false;
796 : }
797 : file = fopen(name, mode);
798 : return file!=nullptr;
799 : }
800 : #else
801 : bool opentemp(const char *, const char *)
802 : {
803 : if(file)
804 : {
805 : return false;
806 : }
807 : file = tmpfile();
808 : return file!=nullptr;
809 : }
810 : #endif
811 11 : void close() final
812 : {
813 11 : if(file)
814 : {
815 2 : fclose(file);
816 2 : file = nullptr;
817 : }
818 11 : }
819 :
820 0 : bool end() final
821 : {
822 0 : return feof(file)!=0;
823 : }
824 :
825 0 : offset tell() const final
826 : {
827 : #ifdef WIN32
828 : #if defined(__GNUC__) && !defined(__MINGW32__)
829 : offset off = ftello64(file);
830 : #else
831 : offset off = _ftelli64(file);
832 : #endif
833 : #else
834 0 : offset off = ftello(file);
835 : #endif
836 : // ftello returns LONG_MAX for directories on some platforms
837 0 : return off + 1 >= 0 ? off : -1;
838 : }
839 :
840 0 : bool seek(offset pos, int whence) final
841 : {
842 : #ifdef WIN32
843 : #if defined(__GNUC__) && !defined(__MINGW32__)
844 : return fseeko64(file, pos, whence) >= 0;
845 : #else
846 : return _fseeki64(file, pos, whence) >= 0;
847 : #endif
848 : #else
849 0 : return fseeko(file, pos, whence) >= 0;
850 : #endif
851 : }
852 :
853 0 : size_t read(void *buf, size_t len) final
854 : {
855 0 : return fread(buf, 1, len, file);
856 : }
857 :
858 0 : size_t write(const void *buf, size_t len) final
859 : {
860 0 : return fwrite(buf, 1, len, file);
861 : }
862 :
863 0 : bool flush() final
864 : {
865 0 : return !fflush(file);
866 : }
867 :
868 0 : int getchar() final
869 : {
870 0 : return fgetc(file);
871 : }
872 :
873 0 : bool putchar(int c) final
874 : {
875 0 : return fputc(c, file)!=EOF;
876 : }
877 :
878 1454 : bool getline(char *str, size_t len) final
879 : {
880 1454 : return fgets(str, len, file)!=nullptr;
881 : }
882 :
883 0 : bool putstring(const char *str) final
884 : {
885 0 : return fputs(str, file)!=EOF;
886 : }
887 :
888 0 : size_t printf(const char *fmt, ...) final
889 : {
890 : va_list v;
891 0 : va_start(v, fmt);
892 0 : int result = std::vfprintf(file, fmt, v);
893 0 : va_end(v);
894 0 : return std::max<int>(result, 0);
895 : }
896 : };
897 :
898 : static VAR(debuggz, 0, 0, 1); //toggles gz checking routines
899 :
900 : class gzstream final : public stream
901 : {
902 : public:
903 0 : gzstream() : file(nullptr), buf(nullptr), reading(false), writing(false), autoclose(false), crc(0), headersize(0)
904 : {
905 0 : zfile.zalloc = nullptr;
906 0 : zfile.zfree = nullptr;
907 0 : zfile.opaque = nullptr;
908 0 : zfile.next_in = zfile.next_out = nullptr;
909 0 : zfile.avail_in = zfile.avail_out = 0;
910 0 : }
911 :
912 0 : ~gzstream()
913 0 : {
914 0 : close();
915 0 : }
916 :
917 0 : void writeheader()
918 : {
919 0 : uchar header[] = { MAGIC1, MAGIC2, Z_DEFLATED, 0, 0, 0, 0, 0, 0, OS_UNIX };
920 0 : file->write(header, sizeof(header));
921 0 : }
922 :
923 0 : void readbuf(size_t size = BUFSIZE)
924 : {
925 0 : if(!zfile.avail_in)
926 : {
927 0 : zfile.next_in = static_cast<Bytef *>(buf);
928 : }
929 0 : size = std::min<size_t>(size, static_cast<size_t>(&buf[BUFSIZE] - &zfile.next_in[zfile.avail_in]));
930 0 : size_t n = file->read(zfile.next_in + zfile.avail_in, size);
931 0 : if(n > 0)
932 : {
933 0 : zfile.avail_in += n;
934 : }
935 0 : }
936 :
937 0 : uchar readbyte(size_t size = BUFSIZE)
938 : {
939 0 : if(!zfile.avail_in)
940 : {
941 0 : readbuf(size);
942 : }
943 0 : if(!zfile.avail_in)
944 : {
945 0 : return 0;
946 : }
947 0 : zfile.avail_in--;
948 0 : return *static_cast<uchar *>(zfile.next_in++);
949 : }
950 :
951 0 : void skipbytes(size_t n)
952 : {
953 0 : while(n > 0 && zfile.avail_in > 0)
954 : {
955 0 : size_t skipped = std::min<size_t>(n, static_cast<size_t>(zfile.avail_in));
956 0 : zfile.avail_in -= skipped;
957 0 : zfile.next_in += skipped;
958 0 : n -= skipped;
959 : }
960 0 : if(n <= 0)
961 : {
962 0 : return;
963 : }
964 0 : file->seek(n, SEEK_CUR);
965 : }
966 :
967 0 : bool checkheader()
968 : {
969 0 : readbuf(10);
970 0 : if(readbyte() != MAGIC1 || readbyte() != MAGIC2 || readbyte() != Z_DEFLATED)
971 : {
972 0 : return false;
973 : }
974 0 : uchar flags = readbyte();
975 0 : if(flags & F_RESERVED)
976 : {
977 0 : return false;
978 : }
979 0 : skipbytes(6);
980 0 : if(flags & F_EXTRA)
981 : {
982 0 : size_t len = readbyte(512);
983 0 : len |= static_cast<size_t>(readbyte(512))<<8;
984 0 : skipbytes(len);
985 : }
986 0 : if(flags & F_NAME)
987 : {
988 0 : while(readbyte(512))
989 : {
990 : //(empty body)
991 : }
992 : }
993 0 : if(flags & F_COMMENT)
994 : {
995 0 : while(readbyte(512))
996 : {
997 : //(empty body)
998 : }
999 : }
1000 0 : if(flags & F_CRC)
1001 : {
1002 0 : skipbytes(2);
1003 : }
1004 0 : headersize = static_cast<size_t>(file->tell() - zfile.avail_in);
1005 0 : return zfile.avail_in > 0 || !file->end();
1006 : }
1007 :
1008 0 : bool open(stream *f, const char *mode, bool needclose, int level)
1009 : {
1010 0 : if(file)
1011 : {
1012 0 : return false;
1013 : }
1014 0 : for(; *mode; mode++)
1015 : {
1016 0 : if(*mode=='r')
1017 : {
1018 0 : reading = true;
1019 0 : break;
1020 : }
1021 0 : else if(*mode=='w')
1022 : {
1023 0 : writing = true;
1024 0 : break;
1025 : }
1026 : }
1027 0 : if(reading)
1028 : {
1029 0 : if(inflateInit2(&zfile, -MAX_WBITS) != Z_OK)
1030 : {
1031 0 : reading = false;
1032 : }
1033 : }
1034 0 : else if(writing && deflateInit2(&zfile, level, Z_DEFLATED, -MAX_WBITS, std::min<int>(MAX_MEM_LEVEL, 8), Z_DEFAULT_STRATEGY) != Z_OK)
1035 : {
1036 0 : writing = false;
1037 : }
1038 0 : if(!reading && !writing)
1039 : {
1040 0 : return false;
1041 : }
1042 0 : file = f;
1043 0 : crc = crc32(0, nullptr, 0);
1044 0 : buf = new uchar[BUFSIZE];
1045 0 : if(reading)
1046 : {
1047 0 : if(!checkheader())
1048 : {
1049 0 : stopreading();
1050 0 : return false;
1051 : }
1052 : }
1053 0 : else if(writing)
1054 : {
1055 0 : writeheader();
1056 : }
1057 0 : autoclose = needclose;
1058 0 : return true;
1059 : }
1060 :
1061 0 : uint getcrc() final
1062 : {
1063 0 : return crc;
1064 : }
1065 :
1066 0 : void finishreading()
1067 : {
1068 0 : if(!reading)
1069 : {
1070 0 : return;
1071 : }
1072 0 : if(debuggz)
1073 : {
1074 0 : uint checkcrc = 0,
1075 0 : checksize = 0;
1076 0 : for(int i = 0; i < 4; ++i)
1077 : {
1078 0 : checkcrc |= static_cast<uint>(readbyte()) << (i*8);
1079 : }
1080 0 : for(int i = 0; i < 4; ++i)
1081 : {
1082 0 : checksize |= static_cast<uint>(readbyte()) << (i*8);
1083 : }
1084 0 : if(checkcrc != crc)
1085 : {
1086 0 : conoutf(Console_Debug, "gzip crc check failed: read %X, calculated %X", checkcrc, crc);
1087 : }
1088 0 : if(checksize != zfile.total_out)
1089 : {
1090 0 : conoutf(Console_Debug, "gzip size check failed: read %u, calculated %u", checksize, static_cast<uint>(zfile.total_out));
1091 : }
1092 : }
1093 : }
1094 :
1095 0 : void stopreading()
1096 : {
1097 0 : if(!reading)
1098 : {
1099 0 : return;
1100 : }
1101 0 : inflateEnd(&zfile);
1102 0 : reading = false;
1103 : }
1104 :
1105 0 : void finishwriting()
1106 : {
1107 0 : if(!writing)
1108 : {
1109 0 : return;
1110 : }
1111 : for(;;)
1112 : {
1113 0 : int err = zfile.avail_out > 0 ? deflate(&zfile, Z_FINISH) : Z_OK;
1114 0 : if(err != Z_OK && err != Z_STREAM_END)
1115 : {
1116 0 : break;
1117 : }
1118 0 : flushbuf();
1119 0 : if(err == Z_STREAM_END)
1120 : {
1121 0 : break;
1122 : }
1123 0 : }
1124 : uchar trailer[8] =
1125 : {
1126 0 : static_cast<uchar>(crc&0xFF), static_cast<uchar>((crc>>8)&0xFF), static_cast<uchar>((crc>>16)&0xFF), static_cast<uchar>((crc>>24)&0xFF),
1127 0 : static_cast<uchar>(zfile.total_in&0xFF), static_cast<uchar>((zfile.total_in>>8)&0xFF), static_cast<uchar>((zfile.total_in>>16)&0xFF), static_cast<uchar>((zfile.total_in>>24)&0xFF)
1128 0 : };
1129 0 : file->write(trailer, sizeof(trailer));
1130 : }
1131 :
1132 0 : void stopwriting()
1133 : {
1134 0 : if(!writing)
1135 : {
1136 0 : return;
1137 : }
1138 0 : deflateEnd(&zfile);
1139 0 : writing = false;
1140 : }
1141 :
1142 0 : void close() final
1143 : {
1144 0 : if(reading)
1145 : {
1146 0 : finishreading();
1147 : }
1148 0 : stopreading();
1149 0 : if(writing)
1150 : {
1151 0 : finishwriting();
1152 : }
1153 0 : stopwriting();
1154 0 : delete[] buf;
1155 0 : buf = nullptr;
1156 0 : if(autoclose)
1157 : {
1158 0 : if(file)
1159 : {
1160 0 : delete file;
1161 0 : file = nullptr;
1162 : }
1163 : }
1164 0 : }
1165 :
1166 0 : bool end() final
1167 : {
1168 0 : return !reading && !writing;
1169 : }
1170 :
1171 0 : offset tell() const final
1172 : {
1173 0 : return reading ? zfile.total_out : (writing ? zfile.total_in : offset(-1));
1174 : }
1175 :
1176 0 : offset rawtell() const final
1177 : {
1178 0 : return file ? file->tell() : offset(-1);
1179 : }
1180 :
1181 0 : offset size() final
1182 : {
1183 0 : if(!file)
1184 : {
1185 0 : return -1;
1186 : }
1187 0 : offset pos = tell();
1188 0 : if(!file->seek(-4, SEEK_END))
1189 : {
1190 0 : return -1;
1191 : }
1192 0 : uint isize = file->get<uint>();
1193 0 : return file->seek(pos, SEEK_SET) ? isize : offset(-1);
1194 : }
1195 :
1196 0 : offset rawsize() final
1197 : {
1198 0 : return file ? file->size() : offset(-1);
1199 : }
1200 :
1201 0 : bool seek(offset pos, int whence) final
1202 : {
1203 0 : if(writing || !reading)
1204 : {
1205 0 : return false;
1206 : }
1207 :
1208 0 : if(whence == SEEK_END)
1209 : {
1210 : uchar skip[512];
1211 0 : while(read(skip, sizeof(skip)) == sizeof(skip))
1212 : {
1213 : //(empty body)
1214 : }
1215 0 : return !pos;
1216 : }
1217 0 : else if(whence == SEEK_CUR)
1218 : {
1219 0 : pos += zfile.total_out;
1220 : }
1221 0 : if(pos >= static_cast<offset>(zfile.total_out))
1222 : {
1223 0 : pos -= zfile.total_out;
1224 : }
1225 0 : else if(pos < 0 || !file->seek(headersize, SEEK_SET))
1226 : {
1227 0 : return false;
1228 : }
1229 : else
1230 : {
1231 0 : if(zfile.next_in && zfile.total_in <= static_cast<uint>(zfile.next_in - buf))
1232 : {
1233 0 : zfile.avail_in += zfile.total_in;
1234 0 : zfile.next_in -= zfile.total_in;
1235 : }
1236 : else
1237 : {
1238 0 : zfile.avail_in = 0;
1239 0 : zfile.next_in = nullptr;
1240 : }
1241 0 : inflateReset(&zfile);
1242 0 : crc = crc32(0, nullptr, 0);
1243 : }
1244 :
1245 : std::array<uchar, 512> skip;
1246 0 : while(pos > 0)
1247 : {
1248 0 : size_t skipped = static_cast<size_t>(std::min<offset>(pos, static_cast<offset>(skip.size())));
1249 0 : if(read(skip.data(), skipped) != skipped)
1250 : {
1251 0 : stopreading();
1252 0 : return false;
1253 : }
1254 0 : pos -= skipped;
1255 : }
1256 :
1257 0 : return true;
1258 : }
1259 :
1260 0 : size_t read(void *outbuf, size_t len) final
1261 : {
1262 0 : if(!reading || !outbuf || !len)
1263 : {
1264 0 : return 0;
1265 : }
1266 0 : zfile.next_out = static_cast<Bytef *>(outbuf);
1267 0 : zfile.avail_out = len;
1268 0 : while(zfile.avail_out > 0)
1269 : {
1270 0 : if(!zfile.avail_in)
1271 : {
1272 0 : readbuf(BUFSIZE);
1273 0 : if(!zfile.avail_in)
1274 : {
1275 0 : stopreading();
1276 0 : break;
1277 : }
1278 : }
1279 0 : int err = inflate(&zfile, Z_NO_FLUSH);
1280 0 : if(err == Z_STREAM_END)
1281 : {
1282 0 : crc = crc32(crc, static_cast<Bytef *>(outbuf), len - zfile.avail_out);
1283 0 : finishreading();
1284 0 : stopreading();
1285 0 : return len - zfile.avail_out;
1286 : }
1287 0 : else if(err != Z_OK)
1288 : {
1289 0 : stopreading();
1290 0 : break;
1291 : }
1292 : }
1293 0 : crc = crc32(crc, reinterpret_cast<Bytef *>(outbuf), len - zfile.avail_out);
1294 0 : return len - zfile.avail_out;
1295 : }
1296 :
1297 0 : bool flushbuf(bool full = false)
1298 : {
1299 0 : if(full)
1300 : {
1301 0 : deflate(&zfile, Z_SYNC_FLUSH);
1302 : }
1303 0 : if(zfile.next_out && zfile.avail_out < BUFSIZE)
1304 : {
1305 0 : if(file->write(buf, BUFSIZE - zfile.avail_out) != BUFSIZE - zfile.avail_out || (full && !file->flush()))
1306 : {
1307 0 : return false;
1308 : }
1309 : }
1310 0 : zfile.next_out = buf;
1311 0 : zfile.avail_out = BUFSIZE;
1312 0 : return true;
1313 : }
1314 :
1315 0 : bool flush() final
1316 : {
1317 0 : return flushbuf(true);
1318 : }
1319 :
1320 0 : size_t write(const void *newbuf, size_t len) final
1321 : {
1322 0 : if(!writing || !newbuf || !len)
1323 : {
1324 0 : return 0;
1325 : }
1326 0 : zfile.next_in = static_cast<Bytef *>(const_cast<void *>(newbuf)); //cast away constness, then to Bytef
1327 0 : zfile.avail_in = len;
1328 0 : while(zfile.avail_in > 0)
1329 : {
1330 0 : if(!zfile.avail_out && !flushbuf())
1331 : {
1332 0 : stopwriting();
1333 0 : break;
1334 : }
1335 0 : int err = deflate(&zfile, Z_NO_FLUSH);
1336 0 : if(err != Z_OK)
1337 : {
1338 0 : stopwriting();
1339 0 : break;
1340 : }
1341 : }
1342 0 : crc = crc32(crc, static_cast<Bytef *>(const_cast<void *>(newbuf)), len - zfile.avail_in);
1343 0 : return len - zfile.avail_in;
1344 : }
1345 :
1346 : private:
1347 : enum GzHeader
1348 : {
1349 : MAGIC1 = 0x1F,
1350 : MAGIC2 = 0x8B,
1351 : BUFSIZE = 16384,
1352 : OS_UNIX = 0x03
1353 : };
1354 :
1355 : enum GzFlags
1356 : {
1357 : F_ASCII = 0x01,
1358 : F_CRC = 0x02,
1359 : F_EXTRA = 0x04,
1360 : F_NAME = 0x08,
1361 : F_COMMENT = 0x10,
1362 : F_RESERVED = 0xE0
1363 : };
1364 :
1365 : stream *file;
1366 : z_stream zfile;
1367 : uchar *buf;
1368 : bool reading, writing, autoclose;
1369 : uint crc;
1370 : size_t headersize;
1371 : };
1372 :
1373 11 : stream *openrawfile(const char *filename, const char *mode)
1374 : {
1375 11 : const char *found = findfile(filename, mode);
1376 11 : if(!found)
1377 : {
1378 0 : return nullptr;
1379 : }
1380 11 : filestream *file = new filestream;
1381 11 : if(!file->open(found, mode))
1382 : {
1383 9 : delete file;
1384 9 : return nullptr;
1385 : }
1386 2 : return file;
1387 : }
1388 :
1389 11 : stream *openfile(const char *filename, const char *mode)
1390 : {
1391 11 : stream *s = openzipfile(filename, mode);
1392 11 : if(s)
1393 : {
1394 0 : return s;
1395 : }
1396 11 : return openrawfile(filename, mode);
1397 : }
1398 :
1399 0 : stream *opengzfile(const char *filename, const char *mode, stream *file, int level)
1400 : {
1401 0 : stream *source = file ? file : openfile(filename, mode);
1402 0 : if(!source)
1403 : {
1404 0 : return nullptr;
1405 : }
1406 0 : gzstream *gz = new gzstream;
1407 0 : if(!gz->open(source, mode, !file, level))
1408 : {
1409 0 : if(!file)
1410 : {
1411 0 : delete source;
1412 : }
1413 0 : delete gz;
1414 0 : return nullptr;
1415 : }
1416 0 : return gz;
1417 : }
1418 :
1419 7 : char *loadfile(const char *fn, size_t *size, bool utf8)
1420 : {
1421 7 : stream *f = openfile(fn, "rb");
1422 7 : if(!f)
1423 : {
1424 7 : return nullptr;
1425 : }
1426 0 : stream::offset fsize = f->size();
1427 0 : if(fsize <= 0)
1428 : {
1429 0 : delete f;
1430 0 : return nullptr;
1431 : }
1432 0 : size_t len = fsize;
1433 0 : char *buf = new char[len+1];
1434 0 : if(!buf)
1435 : {
1436 0 : delete f;
1437 0 : return nullptr;
1438 : }
1439 0 : size_t offset = 0;
1440 0 : if(utf8 && len >= 3)
1441 : {
1442 0 : if(f->read(buf, 3) != 3)
1443 : {
1444 0 : delete f;
1445 0 : delete[] buf;
1446 0 : return nullptr;
1447 : }
1448 0 : if((reinterpret_cast<uchar*>(buf))[0] == 0xEF &&
1449 0 : (reinterpret_cast<uchar*>(buf))[1] == 0xBB &&
1450 0 : (reinterpret_cast<uchar*>(buf))[2] == 0xBF)
1451 : {
1452 0 : len -= 3;
1453 : }
1454 : else
1455 : {
1456 0 : offset += 3;
1457 : }
1458 : }
1459 0 : size_t rlen = f->read(&buf[offset], len-offset);
1460 0 : delete f;
1461 0 : if(rlen != len-offset)
1462 : {
1463 0 : delete[] buf;
1464 0 : return nullptr;
1465 : }
1466 0 : buf[len] = '\0';
1467 0 : if(size!=nullptr)
1468 : {
1469 0 : *size = len;
1470 : }
1471 0 : return buf;
1472 : }
1473 :
|