1 /*
2 *
3 * Iter Vehemens ad Necem (IVAN)
4 * Copyright (C) Timo Kiviluoto
5 * Released under the GNU General
6 * Public License
7 *
8 * See LICENSING which should be included
9 * along with this file for more details
10 *
11 */
12
13 #include <cctype>
14
15 #ifdef WIN32
16 #include <windows.h>
17 #include <direct.h> // for _mkdir
18 #else
19 #include <dirent.h>
20 #include <sys/stat.h> // for mkdir
21 #endif
22
23 #include "save.h"
24 #include "femath.h"
25
26 #include "dbgmsgproj.h"
27
28 truth outputfile::bakcupBeforeSaving = true;
29 truth outputfile::saveOnNewFileAlways = true;
30
Close()31 void outputfile::Close() { DBG3(FileNameNewTmp.CStr(),FileName.CStr(),saveOnNewFileAlways);
32 File.close();
33
34 if(saveOnNewFileAlways){ // This moves the new .tmp file to the correct filename, before removing it.
35 if(FileNameNewTmp.IsEmpty())
36 ABORT("new tmp filename is empty, 'save on new file always' option was properly set before this output file '%s' ?",FileName.CStr());
37
38 std::ifstream newTmpFile(FileNameNewTmp.CStr(), std::ios::binary);
39 if(newTmpFile.good()){
40 std::remove(FileName.CStr()); //just to not let existing one create any doubts if some crash happens here
41
42 std::ofstream final(FileName.CStr(), std::ios::binary);
43 final << newTmpFile.rdbuf();
44
45 final.close();
46 newTmpFile.close();
47
48 std::remove(FileNameNewTmp.CStr()); //last thing
49 }else{
50 ABORT("the new tmp file '%s' should exist!",FileNameNewTmp.CStr());
51 }
52 }
53 }
54
outputfile(cfestring & fileName,truth AbortOnErr)55 outputfile::outputfile(cfestring& fileName, truth AbortOnErr)
56 : FileName(fileName)
57 {
58 if(FileName.IsEmpty())
59 ABORT("empty output filename");
60
61 // If FileName contains a directory, make sure the directory exists first.
62 festring::csizetype LastPathSeparatorPos = FileName.FindLast('/');
63 if(LastPathSeparatorPos != festring::NPos)
64 {
65 festring DirectoryPath = FileName;
66 DirectoryPath.Resize(LastPathSeparatorPos);
67 MakePath(DirectoryPath);
68 }
69
70 if(bakcupBeforeSaving){ // this is not that useful, the tmp files are better as they prevent overwriting the final files in case the game crashes
71 std::ifstream orig(FileName.CStr(), std::ios::binary);
72 if(orig.good()){
73 festring fsBkp;fsBkp << FileName << ".bkp";
74 std::remove(fsBkp.CStr()); //just to have a granted clean new backup
75
76 std::ofstream bkp(fsBkp.CStr(), std::ios::binary);
77 bkp << orig.rdbuf();
78
79 orig.close();
80 bkp.close();
81
82 // backups are kept until leaving the game
83 }
84 }
85
86 festring FileNameNew(FileName);
87 if(saveOnNewFileAlways){
88 FileNameNew<<".tmp";
89 FileNameNewTmp<<FileNameNew;
90
91 /**
92 * if happened a crash and a tmp file is still there,
93 * it is most probably invalid and incomplete,
94 * so just remove it.
95 */
96 std::remove(FileNameNew.CStr());
97 }
98
99 File.open(FileNameNew.CStr(), std::ios::binary);
100
101 if(AbortOnErr && !IsOpen())
102 ABORT("Can't open %s for output!", FileNameNew.CStr());
103 }
104
inputfile(cfestring & FileName,const valuemap * ValueMap,truth AbortOnErr)105 inputfile::inputfile(cfestring& FileName,
106 const valuemap* ValueMap,
107 truth AbortOnErr)
108 : File(FileName.CStr(), std::ios::binary),
109 FileName(FileName),
110 ValueMap(ValueMap),
111 lastWordWasString(false)
112 {
113 if(AbortOnErr && !IsOpen())
114 ABORT("File %s not found!", FileName.CStr());
115 }
116
ReadWord(truth AbortOnEOF)117 festring inputfile::ReadWord(truth AbortOnEOF)
118 {
119 /*static*/ festring ToReturn;
120 ReadWord(ToReturn, AbortOnEOF);
121 return ToReturn;
122 }
123
124 #define MODE_WORD 1
125 #define MODE_NUMBER 2
126
127 #define PUNCT_RETURN 0
128 #define PUNCT_CONTINUE 1
129
HandlePunct(festring & String,int Char,int Mode)130 int inputfile::HandlePunct(festring& String, int Char, int Mode)
131 {
132 if(Char == '/')
133 {
134 if(!Eof())
135 {
136 Char = Get();
137
138 if(Char == '*')
139 {
140 long StartPos = TellPos();
141 int OldChar = 0, CommentLevel = 1;
142
143 for(;;)
144 {
145 if(Eof())
146 ABORT("Unterminated comment in file %s, beginning at line %ld!",
147 FileName.CStr(), TellLineOfPos(StartPos));
148
149 Char = Get();
150
151 if(OldChar != '*' || Char != '/')
152 {
153 if(OldChar != '/' || Char != '*')
154 OldChar = Char;
155 else
156 {
157 ++CommentLevel;
158 OldChar = 0;
159 }
160 }
161 else
162 {
163 if(!--CommentLevel)
164 break;
165 else
166 OldChar = 0;
167 }
168 }
169
170 return PUNCT_CONTINUE;
171 }
172 else
173 {
174 File.putback(Char);
175 File.clear();
176 }
177 }
178
179 if(Mode)
180 File.putback('/');
181 else
182 String << '/';
183
184 return PUNCT_RETURN;
185 }
186
187 if(Mode)
188 {
189 File.putback(Char);
190 return PUNCT_RETURN;
191 }
192
193 if(Char == '"')
194 {
195 lastWordWasString = true;
196 long StartPos = TellPos();
197 int OldChar = 0;
198
199 for(;;)
200 {
201 if(Eof())
202 ABORT("Unterminated string in file %s, beginning at line %ld!",
203 FileName.CStr(), TellLineOfPos(StartPos));
204
205 Char = Get();
206
207 if(Char != '"')
208 {
209 String << char(Char);
210 OldChar = Char;
211 }
212 else if(OldChar == '\\')
213 {
214 String[String.GetSize() - 1] = '"';
215 OldChar = 0;
216 }
217 else
218 return PUNCT_RETURN;
219 }
220 }
221
222 String << char(Char);
223 return PUNCT_RETURN;
224 }
225
ReadWord(festring & String,truth AbortOnEOF)226 void inputfile::ReadWord(festring& String, truth AbortOnEOF)
227 {
228 int Mode = 0;
229 String.Empty();
230 lastWordWasString = false;
231
232 for(int Char = Get(); !Eof(); Char = Get())
233 {
234 if(isalpha(Char) || Char == '_')
235 {
236 if(!Mode)
237 Mode = MODE_WORD;
238 else if(Mode == MODE_NUMBER)
239 {
240 File.putback(Char);
241 return;
242 }
243
244 String << char(Char);
245 continue;
246 }
247
248 if(isdigit(Char))
249 {
250 if(!Mode)
251 Mode = MODE_NUMBER;
252 else if(Mode == MODE_WORD)
253 {
254 File.putback(Char);
255 return;
256 }
257
258 String << char(Char);
259 continue;
260 }
261
262 if((Char == ' ' || Char == '\n') && Mode)
263 return;
264
265 if(ispunct(Char) && HandlePunct(String, Char, Mode) == PUNCT_RETURN)
266 return;
267 }
268
269 if(AbortOnEOF)
270 ABORT("Unexpected end of file %s!", FileName.CStr());
271
272 if(Mode)
273 File.clear();
274 }
275
ReadLetter(truth AbortOnEOF)276 char inputfile::ReadLetter(truth AbortOnEOF)
277 {
278 for(int Char = Get(); !Eof(); Char = Get())
279 {
280 if(isalpha(Char) || isdigit(Char))
281 return Char;
282
283 if(ispunct(Char))
284 {
285 if(Char == '/')
286 {
287 if(!Eof())
288 {
289 Char = Get();
290
291 if(Char == '*')
292 {
293 long StartPos = TellPos();
294 int OldChar = 0, CommentLevel = 1;
295
296 for(;;)
297 {
298 if(Eof())
299 ABORT("Unterminated comment in file %s, "
300 "beginning at line %ld!",
301 FileName.CStr(), TellLineOfPos(StartPos));
302
303 Char = Get();
304
305 if(OldChar != '*' || Char != '/')
306 {
307 if(OldChar != '/' || Char != '*')
308 OldChar = Char;
309 else
310 {
311 ++CommentLevel;
312 OldChar = 0;
313 }
314 }
315 else
316 {
317 if(!--CommentLevel)
318 break;
319 else
320 OldChar = 0;
321 }
322 }
323
324 continue;
325 }
326 else
327 File.putback(Char);
328 }
329
330 return '/';
331 }
332
333 return Char;
334 }
335 }
336
337 if(AbortOnEOF)
338 ABORT("Unexpected end of file %s!", FileName.CStr());
339
340 return 0;
341 }
342
343 /* Reads a number or a formula from inputfile. Valid values could be for
344 instance "3", "5 * 4+5", "2+Variable%4" etc. */
345
ReadNumberIntr(int CallLevel,long * num,truth * isString,truth allowStr,truth PreserveTerminator)346 festring inputfile::ReadNumberIntr(int CallLevel, long *num, truth *isString, truth allowStr, truth PreserveTerminator)
347 {
348 long Value = 0;
349 /*static*/ festring Word;
350 truth NumberCorrect = false;
351 truth firstWord = true;
352 *isString = false;
353 *num = 0;
354 festring res;
355
356 for(;;)
357 {
358 ReadWord(Word);
359 if(firstWord)
360 {
361 if(allowStr && lastWordWasString)
362 {
363 *isString = true;
364 return Word;
365 }
366 firstWord = false;
367 }
368 char First = Word[0];
369
370 if(isdigit(First))
371 {
372 Value = atoi(Word.CStr());
373 NumberCorrect = true;
374 continue;
375 }
376
377 if(Word.GetSize() == 1)
378 {
379 if(First == ';' || First == ',' || First == ':')
380 {
381 if(CallLevel != HIGHEST || PreserveTerminator)
382 File.putback(First);
383
384 *num = Value;
385 return res;
386 }
387
388 if(First == ')')
389 {
390 if((CallLevel != HIGHEST && CallLevel != 4) || PreserveTerminator)
391 File.putback(')');
392
393 *num = Value;
394 return res;
395 }
396
397 if(First == '~')
398 {
399 Value = ~ReadNumber(4);
400 NumberCorrect = true;
401 continue;
402 }
403
404 /* Convert this into an inline function! */
405
406 #define CHECK_OP(op, cl)\
407 if(First == #op[0])\
408 {\
409 if(cl < CallLevel)\
410 {\
411 Value op##= ReadNumber(cl);\
412 NumberCorrect = true;\
413 continue;\
414 }\
415 else\
416 {\
417 File.putback(#op[0]);\
418 *num = Value;\
419 return res;\
420 }\
421 }
422
423 CHECK_OP(&, 1); CHECK_OP(|, 1); CHECK_OP(^, 1);
424 CHECK_OP(*, 2); CHECK_OP(/, 2); CHECK_OP(%, 2);
425 CHECK_OP(+, 3); CHECK_OP(-, 3);
426
427 if(First == '<')
428 {
429 char Next = Get();
430
431 if(Next == '<')
432 if(1 < CallLevel)
433 {
434 Value <<= ReadNumber(1);
435 NumberCorrect = true;
436 continue;
437 }
438 else
439 {
440 File.putback('<');
441 File.putback('<');
442 *num = Value;
443 return res;
444 }
445 else
446 File.putback(Next);
447 }
448
449 if(First == '>')
450 {
451 char Next = Get();
452
453 if(Next == '>')
454 if(1 < CallLevel)
455 {
456 Value >>= ReadNumber(1);
457 NumberCorrect = true;
458 continue;
459 }
460 else
461 {
462 File.putback('>');
463 File.putback('>');
464 *num = Value;
465 return res;
466 }
467 else
468 File.putback(Next);
469 }
470
471 if(First == '(')
472 {
473 if(NumberCorrect)
474 {
475 File.putback('(');
476 *num = Value;
477 return res;
478 }
479 else
480 {
481 Value = ReadNumber(4);
482 NumberCorrect = false;
483 continue;
484 }
485 }
486
487 if(First == '=' && CallLevel == HIGHEST)
488 continue;
489
490 if(First == '#') // for #defines
491 {
492 File.putback('#');
493 *num = Value;
494 return res;
495 }
496 }
497
498 if(Word == "rgb")
499 {
500 int Bits = ReadNumber();
501
502 if(Bits == 16)
503 {
504 int Red = ReadNumber();
505 int Green = ReadNumber();
506 int Blue = ReadNumber();
507 Value = MakeRGB16(Red, Green, Blue);
508 }
509 else if(Bits == 24)
510 {
511 int Red = ReadNumber();
512 int Green = ReadNumber();
513 int Blue = ReadNumber();
514 Value = MakeRGB24(Red, Green, Blue);
515 }
516 else
517 ABORT("Illegal RGB bit size %d in file %s, line %ld!",
518 Bits, FileName.CStr(), TellLine());
519
520 NumberCorrect = true;
521 continue;
522 }
523
524 if(Word == "true")
525 {
526 Value = 1;
527 NumberCorrect = true;
528 continue;
529 }
530
531 if(Word == "false")
532 {
533 Value = 0;
534 NumberCorrect = true;
535 continue;
536 }
537
538 if(ValueMap)
539 {
540 valuemap::const_iterator Iterator = ValueMap->find(Word);
541
542 if(Iterator != ValueMap->end())
543 {
544 Value = Iterator->second;
545 NumberCorrect = true;
546 continue;
547 }
548 }
549
550 ABORT("Odd numeric value \"%s\" encountered in file %s, line %ld!",
551 Word.CStr(), FileName.CStr(), TellLine());
552 }
553 }
554
ReadNumber(int CallLevel,truth PreserveTerminator)555 long inputfile::ReadNumber(int CallLevel, truth PreserveTerminator)
556 {
557 long num = 0;
558 truth isString = false;
559 ReadNumberIntr(CallLevel, &num, &isString, false, PreserveTerminator);
560 return num;
561 }
562
ReadStringOrNumber(long * num,truth * isString,truth PreserveTerminator)563 festring inputfile::ReadStringOrNumber(long *num, truth *isString, truth PreserveTerminator)
564 {
565 return ReadNumberIntr(0xFF, num, isString, true, PreserveTerminator);
566 }
567
ReadVector2d()568 v2 inputfile::ReadVector2d()
569 {
570 v2 Vector;
571 Vector.X = ReadNumber();
572 Vector.Y = ReadNumber();
573 return Vector;
574 }
575
ReadRect()576 rect inputfile::ReadRect()
577 {
578 rect Rect;
579 Rect.X1 = ReadNumber();
580 Rect.Y1 = ReadNumber();
581 Rect.X2 = ReadNumber();
582 Rect.Y2 = ReadNumber();
583 return Rect;
584 }
585
operator <<(outputfile & SaveFile,cfestring & String)586 outputfile& operator<<(outputfile& SaveFile, cfestring& String)
587 {
588 ushort Length = String.GetSize();
589 SaveFile << Length;
590
591 if(Length)
592 SaveFile.Write(String.CStr(), Length);
593
594 return SaveFile;
595 }
596
operator >>(inputfile & SaveFile,festring & String)597 inputfile& operator>>(inputfile& SaveFile, festring& String)
598 {
599 char* RealBuffer, StackBuffer[1024];
600 ushort Length;
601 SaveFile >> Length;
602 RealBuffer = Length < 1024 ? StackBuffer : new char[Length + 1];
603 String.Empty();
604
605 if(Length)
606 {
607 SaveFile.Read(RealBuffer, Length);
608 RealBuffer[Length] = 0;
609 String << RealBuffer;
610 }
611
612 if(Length >= 1024)
613 delete [] RealBuffer;
614
615 return SaveFile;
616 }
617
operator <<(outputfile & SaveFile,cchar * String)618 outputfile& operator<<(outputfile& SaveFile, cchar* String)
619 {
620 ushort Length = String ? strlen(String) : 0;
621 SaveFile << Length;
622
623 if(Length)
624 SaveFile.Write(String, Length);
625
626 return SaveFile;
627 }
628
operator >>(inputfile & SaveFile,char * & String)629 inputfile& operator>>(inputfile& SaveFile, char*& String)
630 {
631 ushort Length;
632 SaveFile >> Length;
633
634 if(Length)
635 {
636 String = new char[Length + 1];
637 SaveFile.Read(String, Length);
638 String[Length] = 0;
639 }
640 else
641 String = 0;
642
643 return SaveFile;
644 }
645
ReadData(festring & String,inputfile & SaveFile)646 void ReadData(festring& String, inputfile& SaveFile)
647 {
648 SaveFile.ReadWord(String);
649
650 if(String == "=")
651 SaveFile.ReadWord(String);
652
653 SaveFile.ReadWord();
654 }
655
ReadData(fearray<long> & Array,inputfile & SaveFile)656 void ReadData(fearray<long>& Array, inputfile& SaveFile)
657 {
658 Array.Clear();
659 /*static*/ festring Word;
660 SaveFile.ReadWord(Word);
661
662 if(Word == "=")
663 SaveFile.ReadWord(Word);
664
665 if(Word == "=")
666 {
667 Array.Allocate(1);
668 Array.Data[0] = SaveFile.ReadNumber();
669 return;
670 }
671
672 if(Word != "{")
673 ABORT("Array syntax error \"%s\" found in file %s, line %ld!",
674 Word.CStr(), SaveFile.GetFileName().CStr(), SaveFile.TellLine());
675
676 fearray<long>::sizetype Size = SaveFile.ReadNumber();
677 Array.Allocate(Size);
678
679 for(fearray<long>::sizetype c = 0; c < Size; ++c)
680 Array.Data[c] = SaveFile.ReadNumber();
681
682 if(SaveFile.ReadWord() != "}")
683 ABORT("Illegal array terminator \"%s\" "
684 "encountered in file %s, line %ld!",
685 Word.CStr(), SaveFile.GetFileName().CStr(),
686 SaveFile.TellLine());
687 }
688
ReadData(fearray<festring> & Array,inputfile & SaveFile)689 void ReadData(fearray<festring>& Array, inputfile& SaveFile)
690 {
691 Array.Clear();
692 /*static*/ festring Word;
693 SaveFile.ReadWord(Word);
694
695 if(Word == "=")
696 SaveFile.ReadWord(Word);
697
698 if(Word == "=")
699 {
700 Array.Allocate(1);
701 SaveFile.ReadWord(Array.Data[0]);
702
703 if(SaveFile.ReadWord() != ";")
704 ABORT("Array syntax error \"%s\" found in file %s, line %ld!",
705 Word.CStr(), SaveFile.GetFileName().CStr(), SaveFile.TellLine());
706
707 return;
708 }
709
710 if(Word != "{")
711 ABORT("Array syntax error \"%s\" found in file %s, line %ld!",
712 Word.CStr(), SaveFile.GetFileName().CStr(), SaveFile.TellLine());
713
714 fearray<festring>::sizetype Size = SaveFile.ReadNumber();
715 Array.Allocate(Size);
716
717 for(fearray<festring>::sizetype c = 0; c < Size; ++c)
718 {
719 SaveFile.ReadWord(Array.Data[c]);
720 SaveFile.ReadWord(Word);
721
722 if(Word != "," && Word != ";")
723 ABORT("Array syntax error \"%s\" found in file %s, line %ld!",
724 Word.CStr(), SaveFile.GetFileName().CStr(), SaveFile.TellLine());
725 }
726
727 if(SaveFile.ReadWord() != "}")
728 ABORT("Illegal array terminator \"%s\" "
729 "encountered in file %s, line %ld!",
730 Word.CStr(), SaveFile.GetFileName().CStr(),
731 SaveFile.TellLine());
732 }
733
TellLineOfPos(long Pos)734 ulong inputfile::TellLineOfPos(long Pos)
735 {
736 ulong Line = 1;
737 long BackupPos = TellPos();
738 SeekPosBegin(0);
739
740 while(TellPos() != Pos)
741 if(Get() == '\n')
742 ++Line;
743
744 if(TellPos() != BackupPos)
745 SeekPosBegin(BackupPos);
746
747 return Line;
748 }
749
750 /* Returns a vector of absolute file paths referring to files inside Directory
751 * with the given Extension. */
752
ListFiles(festring Directory,cfestring & Extension)753 std::vector<festring> ListFiles(festring Directory, cfestring& Extension)
754 {
755 std::vector<festring> Files;
756 if(Directory[Directory.GetSize() - 1] != '/')
757 Directory << '/';
758
759 #ifdef WIN32
760 festring SearchPath = Directory + "*" + Extension;
761 WIN32_FIND_DATA fd;
762 HANDLE hFind = ::FindFirstFile(SearchPath.CStr(), &fd);
763 if(hFind == INVALID_HANDLE_VALUE)
764 ABORT("Cannot open directory %s", Directory.CStr());
765
766 do
767 {
768 if(!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
769 Files.push_back(Directory + fd.cFileName);
770 }
771 while(::FindNextFile(hFind, &fd));
772 ::FindClose(hFind);
773 #else
774 #ifdef __DJGPP__
775 #error "ListFiles() not implemented on DOS!"
776 #else
777 DIR* dirp = opendir(Directory.CStr());
778 if(!dirp)
779 ABORT("Cannot open directory %s", Directory.CStr());
780
781 dirent* dp;
782 while((dp = readdir(dirp)) != nullptr)
783 {
784 cint ExtIndex = strlen(dp->d_name) - Extension.GetSize();
785 if(ExtIndex < 0) continue;
786 if(strcmp(dp->d_name + ExtIndex, Extension.CStr()) == 0)
787 Files.push_back(Directory + dp->d_name);
788 }
789 closedir(dirp);
790 #endif
791 #endif
792 return Files;
793 }
794
795 /* Postcondition: Path exists and is a directory. */
796
MakePath(cfestring & Path)797 void MakePath(cfestring& Path)
798 {
799 for(festring::sizetype Pos = 0; Pos != Path.GetSize();)
800 {
801 Pos = Path.Find('/', Pos + 1);
802 if (Pos == festring::NPos) Pos = Path.GetSize();
803 cfestring DirectoryPath = festring(Path.CStr(), Pos) + '\0';
804 #ifdef WIN32
805 _mkdir(DirectoryPath.CStr());
806 #else
807 mkdir(DirectoryPath.CStr(), S_IRWXU|S_IRWXG);
808 #endif
809 }
810 }
811
GetUserDataDir()812 festring GetUserDataDir()
813 {
814 #ifdef UNIX
815 festring Dir;
816 #ifdef MAC_APP
817 Dir << getenv("HOME") << "/Library/Application Support/IVAN/";
818 #else
819 char *xdg_home = getenv("XDG_DATA_HOME"); // check if XDG_DATA_HOME is set
820 if (xdg_home != NULL) { // if it is, use that directory
821 Dir << xdg_home << "/ivan/";
822 } else { // otherwise, default to home directory
823 Dir << getenv("HOME") << "/.ivan/";
824 }
825 #endif /* MAC_APP */
826 #ifdef DBGMSG
827 dbgmsg::SetDebugLogPath(Dir.CStr());
828 #endif /* DBGMSG */
829 return Dir;
830 #endif /* UNIX */
831
832 #if defined(WIN32) || defined(__DJGPP__)
833 return "./";
834 #endif
835 }
836