a45267d4797d843b98a0b36d5c9d2303136122ea
[sdk] / ecere / src / sys / EARArchive.ec
1 namespace sys;
2
3 #define uint _uint
4 #include "zlib.h"
5 #undef uint
6
7 import "System"
8 import "BufferedFile"
9
10 #define OFFSET(s, m) ((uint)(uintptr) (&((s *) 0)->m))
11 #define MDWORD(a,b) ((((uint32)((uint16)(b)))<<16)+((uint16)(a)))
12
13 #define EAR_RECOGNITION { 'e', 'A', 'R', 228, 11, 12, 3, 0 }
14
15 static byte earRecognition[] = EAR_RECOGNITION;
16
17 static class FreeBlock : struct
18 {
19    FreeBlock prev, next;
20    uint start, end;
21 };
22
23 static struct EARHeader
24 {
25    byte recognition[sizeof(earRecognition)];
26    uint version                             __attribute__((packed));
27    FileSize totalSize                       __attribute__((packed));
28 };
29
30 static enum EAREntryType { ENTRY_FILE = 1, ENTRY_FOLDER = 2 };
31
32 static struct EAREntry
33 {
34    EAREntryType type             __attribute__((packed));
35    TimeStamp32 created, modified __attribute__((packed));
36    FileSize size, cSize          __attribute__((packed));
37    uint prev, next               __attribute__((packed));
38    uint nameLen                  __attribute__((packed));
39    // null terminated file name follows
40 };
41
42 static File EAROpenArchive(const char * archive, EARHeader header)
43 {
44    File f = null;
45    if(archive[0] == ':')
46    {
47       char moduleName[MAX_LOCATION];
48       const char * name = archive + 1;
49 #if defined(__ANDROID__)
50       if(!name[0])
51          name = ((SubModule)__thisModule.application.modules.first).next.module.name;
52 #endif
53
54 #if defined(__EMSCRIPTEN__)
55       if(!name[0])
56          f = FileOpen("resources.ear", read);
57 #endif
58
59       if(!f && LocateModule(name, moduleName))
60          f = FileOpen(moduleName, read);
61    }
62    else
63       f = FileOpen(archive, read);
64    if(f)
65    {
66       uint archiveSize;
67
68       // First attempt to treat this as an archive file
69       if(f.Read(header, sizeof(EARHeader), 1) == 1 &&
70          !memcmp(header.recognition, earRecognition, sizeof(earRecognition)))
71          return f;
72
73       // Then try to see if an archive is at the end of the file
74       f.Seek(-(int)sizeof(uint), end);
75       f.Read(&archiveSize, sizeof(uint), 1);
76       f.Seek(-(int)archiveSize, end);
77       if(f.Read(header, sizeof(EARHeader), 1) == 1 &&
78          !memcmp(header.recognition, earRecognition, sizeof(earRecognition)))
79          return f;
80
81       delete f;
82    }
83    return null;
84 }
85
86 static FileAttribs EARGetEntry(File f, EAREntry entry, const char * name, char * path)
87 {
88    uint first = 0, last = 0;
89    if(!name[0])
90       return FileAttribs { isDirectory = true };
91    if(!f.Read(&first, sizeof(uint), 1))
92       return 0;
93    f.Read(&last, sizeof(uint), 1);
94    if(first)
95    {
96       char namePart[MAX_FILENAME], nameRest[MAX_LOCATION];
97       SplitDirectory(name, namePart, nameRest);
98
99       if(!strcmp(namePart, "/") || !strcmp(namePart, "\\"))
100          strcpy(namePart, DIR_SEPS);
101
102       f.Seek(first, start);
103       for(;;)
104       {
105          char fileName[MAX_FILENAME];
106
107          f.Read(entry, sizeof(EAREntry), 1);
108          f.Read(fileName, 1, entry.nameLen);
109          fileName[entry.nameLen] = '\0';
110
111          if(!strcmp(fileName, "/") || !strcmp(fileName, "\\"))
112             strcpy(fileName, DIR_SEPS);
113
114          if(!fstrcmp(fileName, namePart))
115          {
116             if(path)
117                PathCat(path, fileName);
118             if(nameRest[0])
119                return EARGetEntry(f, entry, nameRest, path);
120             else
121                return (entry.type == ENTRY_FILE) ? FileAttribs { isFile = true } : FileAttribs { isDirectory = true };
122          }
123          if(entry.next)
124             f.Seek(entry.next, start);
125          else
126             break;
127       }
128    }
129    return FileAttribs { };
130 }
131
132 #if !defined(ECERE_NOARCHIVE) && !defined(ECERE_VANILLA)
133 class EARArchive : Archive
134 {
135    File f;
136    //BufferedFile bf { };
137    // char path[MAX_LOCATION];
138    uint archiveStart;
139    uint rootDir;
140    OldList freeBlocks;
141    bool writeAccess;
142
143    uint Update()
144    {
145       if(rootDir)
146       {
147          uint end;
148
149          end = ((FreeBlock)freeBlocks.last).start;
150
151          // Update header
152          f.Seek(archiveStart + OFFSET(EARHeader, totalSize), start);
153          f.Write(&totalSize, sizeof(uint), 1);
154
155          // Write Footer
156          f.Seek(end, start);
157
158          end += sizeof(uint);
159
160          end -= archiveStart;
161
162          f.Write(&end, sizeof(uint), 1);
163          f.Truncate(archiveStart + end);
164
165          return end;
166       }
167       return 0;
168    }
169
170    ~EARArchive()
171    {
172       if(f && rootDir && writeAccess)
173       {
174          // Perform Defrag
175          Defrag(rootDir);
176          archiveStart += Update();
177       }
178       if(f && writeAccess)
179       {
180          f.Flush();
181          f.Unlock(0, 0, true);
182       }
183       delete f;
184
185       /*if(rootDir && writeAccess)
186       {
187          // Fix the size of the archive
188          FileTruncate(path, archiveStart);
189       }*/
190
191       freeBlocks.Free(null);
192    }
193
194    bool Clear()
195    {
196       rootDir = 0;
197       return true;
198    }
199
200    ArchiveDir OpenDirectory(const char * name, FileStats stats, ArchiveAddMode addMode)
201    {
202       ArchiveDir result = null;
203       EARArchiveDir dir { readOnly = addMode == readOnlyDir };
204       if(dir)
205       {
206          char namePart[MAX_LOCATION] = "", nameRest[MAX_LOCATION];
207
208          dir.archive = this;
209
210          strcpy(nameRest, name);
211
212          if(!strcmp(namePart, "/") || !strcmp(namePart, "\\"))
213             strcpy(namePart, DIR_SEPS);
214
215          // Search for directory
216          if(rootDir)
217          {
218             dir.position = rootDir;
219             if(f.Seek(dir.position, start))
220             {
221                dir.first = 0;
222                dir.last = 0;
223
224                f.Read(&dir.first, sizeof(uint), 1);
225                f.Read(&dir.last, sizeof(uint), 1);
226
227                result = dir;
228             }
229          }
230
231          // If directory doesn't exist already
232          if(!result && addMode != refresh)
233          {
234             rootDir = Position(2*sizeof(uint));
235             dir.position = rootDir;
236          }
237
238          result = dir;
239
240          // Open rest of directory...
241          if(result && nameRest[0])
242          {
243             result = dir.OpenDirectory(nameRest, stats, addMode);
244             delete dir;
245          }
246       }
247       return result;
248    }
249
250    uint Position(uint size)
251    {
252       FreeBlock block;
253       for(block = freeBlocks.first; block; block = block.next)
254       {
255          if(block.end - block.start + 1 >= size)
256          {
257             uint position = block.start;
258             if(block.end - block.start + 1 == size)
259                freeBlocks.Delete(block);
260             else
261                block.start += size;
262             return position;
263          }
264       }
265       return 0;
266    }
267
268    bool DefragOffset(uint * offset)
269    {
270       FreeBlock block;
271       uint subtract = 0;
272       for(block = freeBlocks.first; block; block = block.next)
273       {
274          if(*offset > block.start)
275             subtract += block.end - block.start + 1;
276          else
277             break;
278       }
279       if(subtract)
280       {
281          *offset -= subtract;
282          return true;
283       }
284       return false;
285    }
286
287    #define MAX_BUFFERSIZE 0x400000
288
289    void Defrag(uint dirPosition)
290    {
291       // Update all offsets within the files
292       uint first = 0, last = 0;
293       uint position = 0, next = 0;
294
295       f.Seek(dirPosition, start);
296       f.Read(&first, sizeof(uint), 1);
297       f.Read(&last, sizeof(uint), 1);
298
299       position = first;
300
301       if(first && DefragOffset(&first))
302       {
303          if(f.Seek(dirPosition, start))
304             f.Write(&first, sizeof(uint), 1);
305       }
306       if(last && DefragOffset(&last))
307       {
308          if(f.Seek(dirPosition + sizeof(uint), start))
309             f.Write(&last, sizeof(uint), 1);
310       }
311
312       for(; position; position = next)
313       {
314          EAREntry entry { };
315
316          if(f.Seek(position, start) && f.Read(entry, sizeof(EAREntry), 1))
317          {
318             next = entry.next;
319
320             if(entry.prev && DefragOffset(&entry.prev))
321             {
322                f.Seek(position + OFFSET(EAREntry, prev), start);
323                f.Write(&entry.prev, sizeof(uint), 1);
324             }
325             if(entry.next && DefragOffset(&entry.next))
326             {
327                f.Seek(position + OFFSET(EAREntry, next), start);
328                f.Write(&entry.next, sizeof(uint), 1);
329             }
330
331             if(entry.type == ENTRY_FOLDER)
332                Defrag(position + sizeof(EAREntry) + entry.nameLen);
333          }
334          else
335             return;
336       }
337
338       // Move all the blocks
339       if(dirPosition == rootDir)
340       {
341          uint bufferSize = 0;
342          byte * buffer = null;
343          FreeBlock block, nextBlock;
344          for(block = freeBlocks.first; block && block.next; block = nextBlock)
345          {
346             uint dataSize, c;
347
348             nextBlock = block.next;
349             dataSize = nextBlock.start - (block.end + 1);
350
351             if(dataSize > bufferSize && (!bufferSize || bufferSize < MAX_BUFFERSIZE))
352             {
353                bufferSize = Min(dataSize, MAX_BUFFERSIZE);
354                buffer = renew buffer byte[bufferSize];
355             }
356
357             for(c = 0; c<dataSize; c += bufferSize)
358             {
359                uint size = (dataSize > c + bufferSize) ? bufferSize : (dataSize - c);
360
361                // Read block of data
362                f.Seek((block.end + 1) + c, start);
363                f.Read(buffer, size, 1);
364
365                // Write block of data
366                f.Seek(block.start + c, start);
367                f.Write(buffer, size, 1);
368             }
369
370             nextBlock.start -= (block.end - block.start) + 1;
371
372             freeBlocks.Delete(block);
373          }
374          delete buffer;
375       }
376    }
377
378    uint Find(EARArchiveDir directory, const char * namePart, EAREntry entry)
379    {
380       uint position;
381       for(position = directory.first; position; position = entry.next)
382       {
383          char fileName[MAX_FILENAME];
384
385          if(f.Seek(position, start) && f.Read(entry, sizeof(EAREntry), 1))
386          {
387             if(entry.nameLen > MAX_FILENAME)
388                return 0;   // CORRUPTION ERROR
389             f.Read(fileName, 1, entry.nameLen);
390             fileName[entry.nameLen] = '\0';
391
392             if(!strcmp(fileName, "/") || !strcmp(fileName, "\\"))
393                strcpy(fileName, DIR_SEPS);
394
395             if(!fstrcmp(fileName, namePart))
396                return position;
397          }
398          else
399             return 0;   // ERROR OUT OF SPACE?
400       }
401       return 0;
402    }
403
404    void AddFreeBlock(uint position, uint size)
405    {
406       FreeBlock block, prevBlock, nextBlock = null;
407
408       // Find the previous and next free block
409       prevBlock = null;
410       for(block = freeBlocks.first; block; block = block.next)
411          if(block.end < position)
412             prevBlock = block;
413          else
414          {
415             nextBlock = block;
416             break;
417          }
418
419       // Try to merge with previous block
420       if(prevBlock && prevBlock.end + 1 == position)
421       {
422           prevBlock.end += size;
423          // Try to merge with next block as well
424          if(nextBlock && nextBlock.start == prevBlock.end + 1)
425          {
426             prevBlock.end = nextBlock.end;
427             freeBlocks.Delete(nextBlock);
428          }
429       }
430       // Try to merge with next block
431       else if(nextBlock && nextBlock.start == position + size)
432       {
433          nextBlock.start = position;
434       }
435       // This free block is not connected to any other block
436       else
437       {
438          freeBlocks.Insert(prevBlock, FreeBlock { start = position, end = position + size - 1 });
439       }
440    }
441
442    void SubtractBlock(uint start, uint size)
443    {
444       FreeBlock block;
445       for(block = freeBlocks.first; block; block = block.next)
446       {
447          if(block.end >= start - 1L && block.start <= start + size)
448          {
449             if(block.end > start + size && block.start < start - 1L)
450             {
451                FreeBlock newBlock { start = start + size, end = block.end };
452                block.end = start - 1L;
453                freeBlocks.Insert(block, newBlock);
454             }
455             else if(block.end > start + size)
456             {
457                block.start = start + size;
458             }
459             else if(block.start < start - 1L)
460             {
461                block.end = start - 1L;
462             }
463             else
464             {
465                freeBlocks.Remove(block);
466                delete block;
467             }
468             break;
469          }
470       }
471    }
472
473    void Delete(EARArchiveDir dir, uint position, EAREntry entry)
474    {
475       uint size;
476       if(entry.type == ENTRY_FOLDER)
477       {
478          EARArchiveDir subDir {};
479          uint filePosition;
480          EAREntry fileEntry;
481
482          subDir.position = dir.position;
483          f.Read(&subDir.first, sizeof(uint), 1);
484          f.Read(&subDir.last, sizeof(uint), 1);
485
486          // Erase directory contents first
487          for(filePosition = subDir.first; filePosition; filePosition = fileEntry.next)
488          {
489             f.Seek(filePosition, start);
490             f.Read(&fileEntry, sizeof(EAREntry), 1);
491             f.Seek(fileEntry.nameLen, current);
492             Delete(subDir, filePosition, &fileEntry);
493          }
494          size = sizeof(EAREntry) + entry.nameLen + 2 * sizeof(uint);
495          delete subDir;
496       }
497       else
498          size = sizeof(EAREntry) + entry.nameLen + (entry.cSize ? entry.cSize : entry.size);
499
500       // Unlink this file
501       if(entry.prev)
502       {
503          f.Seek(entry.prev + OFFSET(EAREntry, next), start);
504          f.Write(&entry.next, sizeof(uint), 1);
505       }
506       if(entry.next)
507       {
508          f.Seek(entry.next + OFFSET(EAREntry, prev), start);
509          f.Write(&entry.prev, sizeof(uint), 1);
510       }
511       if(dir.last == position) dir.last = entry.prev;
512       if(dir.first == position) dir.first = entry.next;
513
514       AddFreeBlock(position, size);
515       totalSize -= entry.size;
516
517       // Invalidate Buffer
518       // bf.handle = f;
519    }
520
521    File FileOpen(const char * name)
522    {
523       File result = null;
524       EARFile file {};
525       if(file)
526       {
527          EAREntry entry { };
528
529          f.Seek(archiveStart + sizeof(EARHeader), start);
530          if(EARGetEntry(f, entry, name, null).isFile)
531          {
532             if(entry.cSize)
533             {
534                byte * uncompressed = new byte[entry.size];
535                if(uncompressed)
536                {
537                   byte * compressed = new byte[entry.cSize];
538                   if(compressed)
539                   {
540                      if(f.Read(compressed, 1, entry.cSize) == entry.cSize)
541                      {
542                         unsigned long destLen = entry.size;
543                         uncompress(uncompressed, &destLen, compressed, entry.cSize);
544                         entry.size = (FileSize)destLen;  // TODO: Support 64 bit file sizes
545                      }
546                      delete compressed;
547                   }
548
549                   file.position = 0;
550                   file.size = entry.size;
551                   file.buffer = uncompressed;
552
553                   result = file;
554                }
555             }
556             else
557             {
558                file.start = f.Tell();
559                file.position = 0;
560                file.size = entry.size;
561                file.f = f;
562                incref file.f;
563                file.f.Seek(file.start, start);
564                result = file;
565             }
566          }
567          if(!result)
568             delete file;
569       }
570       return result;
571    }
572
573    File FileOpenAtPosition(uint position)
574    {
575       EARFile file {};
576       char fileName[MAX_LOCATION];
577       EAREntry entry { };
578       f.Seek(position, start);
579       f.Read(entry, sizeof(EAREntry), 1);
580       /*if(entry.nameLen > 1024)
581          printf("");*/
582       f.Read(fileName, 1, entry.nameLen);
583       if(entry.cSize)
584       {
585          byte * uncompressed = new byte[entry.size];
586          if(uncompressed)
587          {
588             byte * compressed = new byte[entry.cSize];
589             if(compressed)
590             {
591                if(f.Read(compressed, 1, entry.cSize) == entry.cSize)
592                {
593                   unsigned long destLen = entry.size;
594                   uncompress(uncompressed, &destLen, compressed, entry.cSize);
595                   entry.size = (FileSize)destLen;
596                }
597                delete compressed;
598             }
599
600             file.position = 0;
601             file.size = entry.size;
602             file.buffer = uncompressed;
603          }
604       }
605       else
606       {
607          file.start = f.Tell();
608          file.position = 0;
609          file.size = entry.size;
610          file.f = f;
611          file.f.Seek(file.start, start);
612          incref file.f;
613       }
614       return file;
615    }
616
617    FileAttribs FileExists(const char * fileName)
618    {
619       FileAttribs result;
620       EAREntry entry { };
621       f.Seek(archiveStart + sizeof(EARHeader), start);
622       result = EARGetEntry(f, entry, fileName, null);
623       return result;
624    }
625
626    void SubtractUsedBlocks()
627    {
628       uint first, last;
629       if(!f.Read(&first, sizeof(uint), 1))
630          return;
631 #ifdef _DEBUG
632       if(first > f.GetSize())
633       {
634          printf("Error\n");
635       }
636 #endif
637       f.Read(&last, sizeof(uint), 1);
638
639       while(first)
640       {
641          uint size = 0;
642
643          char fileName[MAX_FILENAME];
644          EAREntry entry { };
645
646          f.Seek(first, start);
647          f.Read(entry, sizeof(EAREntry), 1);
648          if(entry.nameLen < MAX_FILENAME)
649          {
650             f.Read(fileName, 1, entry.nameLen);
651             fileName[entry.nameLen] = 0;
652          }
653          else
654          {
655             fileName[0] = 0;
656             break;
657          }
658
659          size += sizeof(EAREntry) + entry.nameLen;
660
661          if(entry.type == ENTRY_FILE)
662          {
663             size += entry.cSize ? entry.cSize : entry.size;
664          }
665          else if(entry.type == ENTRY_FOLDER)
666          {
667             size += 2 * sizeof(uint);
668             SubtractUsedBlocks();
669          }
670          SubtractBlock(first, size);
671          first = entry.next;
672       }
673    }
674
675    void SetBufferSize(uint bufferSize)
676    {
677       if(f && f._class == class(BufferedFile))
678          ((BufferedFile)f).bufferSize = bufferSize;
679    }
680
681    void SetBufferRead(uint bufferRead)
682    {
683       if(f && f._class == class(BufferedFile))
684          ((BufferedFile)f).bufferRead = bufferRead;
685    }
686 }
687
688 class EARArchiveDir : ArchiveDir
689 {
690    EARArchive archive;
691    uint position;
692    uint first, last;
693    bool readOnly;
694
695    ~EARArchiveDir()
696    {
697       if(!readOnly)
698       {
699          archive.f.Seek(position, start);
700          archive.f.Write(&first, sizeof(uint), 1);
701          archive.f.Write(&last, sizeof(uint), 1);
702          archive.Update();
703       }
704    }
705
706    File FileOpen(const char * name)
707    {
708       File result = null;
709       EARFile file {};
710       if(file)
711       {
712          EAREntry entry { };
713
714          archive.f.Seek(position, start);
715          if(EARGetEntry(archive.f, entry, name, null).isFile)
716          {
717             if(entry.cSize)
718             {
719                byte * uncompressed = new byte[entry.size];
720                if(uncompressed)
721                {
722                   byte * compressed = new byte[entry.cSize];
723                   if(compressed)
724                   {
725                      if(archive.f.Read(compressed, 1, entry.cSize) == entry.cSize)
726                      {
727                         unsigned long destLen = entry.size;
728                         uncompress(uncompressed, &destLen, compressed, entry.cSize);
729                         entry.size = (FileSize)destLen;
730                      }
731                      delete compressed;
732                   }
733
734                   file.position = 0;
735                   file.size = entry.size;
736                   file.buffer = uncompressed;
737
738                   result = file;
739                }
740             }
741             else
742             {
743                file.start = archive.f.Tell();
744                file.position = 0;
745                file.size = entry.size;
746                file.f = archive.f;
747                file.f.Seek(file.start, start);
748                incref file.f;
749                result = file;
750             }
751          }
752          if(!result)
753             delete file;
754       }
755       return result;
756    }
757
758    FileAttribs FileExists(const char * fileName)
759    {
760       FileAttribs result;
761       EAREntry entry { };
762       archive.f.Seek(position, start);
763       result = EARGetEntry(archive.f, entry, fileName, null);
764       return result;
765    }
766
767    ArchiveDir OpenDirectory(const char * name, FileStats stats, ArchiveAddMode addMode)
768    {
769       ArchiveDir result = null;
770       EARArchiveDir dir { readOnly = addMode == readOnlyDir };
771       if(dir)
772       {
773          char namePart[MAX_LOCATION] = "", nameRest[MAX_LOCATION];
774          uint position;
775          EAREntry entry { };
776
777          dir.archive = archive;
778
779          SplitDirectory(name, namePart, nameRest);
780
781          if(!strcmp(namePart, "/") || !strcmp(namePart, "\\"))
782             strcpy(namePart, DIR_SEPS);
783
784          // Search for directory
785
786          position = archive.Find(this, namePart, entry);
787          if(position)
788          {
789             // Fail if file of same name already exists
790             if(entry.type == ENTRY_FILE)
791                return null;
792             else
793             {
794                dir.position = position + sizeof(EAREntry) + entry.nameLen;
795                dir.first = 0;
796                dir.last = 0;
797
798                archive.f.Read(&dir.first, sizeof(uint), 1);
799                archive.f.Read(&dir.last, sizeof(uint), 1);
800
801                result = dir;
802             }
803          }
804
805          // If directory doesn't exist already
806          if(!result && addMode != refresh)
807          {
808             // Write Header if it's not the root directory
809             EAREntry entry {};
810             uint position;
811
812             entry.nameLen = strlen(namePart);
813             entry.prev = last;
814             entry.next = 0;
815             entry.type = ENTRY_FOLDER;
816             if(!nameRest[0] && stats)
817             {
818                entry.created = (TimeStamp32)stats.created;
819                entry.modified = (TimeStamp32)stats.modified;
820             }
821
822             position = archive.Position(sizeof(EAREntry) + entry.nameLen + 2*sizeof(uint));
823
824             archive.f.Seek(position, start);
825             archive.f.Write(entry, sizeof(EAREntry), 1);
826             archive.f.Write(namePart, entry.nameLen, 1);
827
828             last = position;
829             if(!first) first = position;
830
831             // Update the next pointer of previous entry
832             if(entry.prev)
833             {
834                archive.f.Seek(entry.prev + OFFSET(EAREntry, next), start);
835                archive.f.Write(&position, sizeof(uint), 1);
836             }
837
838             // Make the dir position point after the header
839             dir.position = position + sizeof(EAREntry) + entry.nameLen;
840          }
841          // Just update the time stamps
842          else if(result && !nameRest[0] && stats)
843          {
844             archive.f.Seek(position + OFFSET(EAREntry, created), start);
845             archive.f.Write(&stats.created, sizeof(uint), 1);
846             archive.f.Write(&stats.modified, sizeof(uint), 1);
847          }
848          result = dir;
849
850          // Open rest of directory...
851          if(result && nameRest[0])
852          {
853             result = dir.OpenDirectory(nameRest, stats, addMode);
854             delete dir;
855          }
856       }
857       return result;
858    }
859
860    bool Delete(const char * name)
861    {
862       EAREntry entry { };
863       uint position;
864       char namePart[MAX_LOCATION];
865
866       strcpy(namePart, name);
867       if(!strcmp(namePart, "/") || !strcmp(namePart, "\\"))
868          strcpy(namePart, DIR_SEPS);
869
870       position = archive.Find(this, namePart, entry);
871       if(position)
872       {
873          archive.Delete(this, position, entry);
874          return true;
875       }
876       return false;
877    }
878
879    bool Move(const char * name, EARArchiveDir to)
880    {
881       bool result = false;
882       if(position != to.position)
883       {
884          EAREntry entry { };
885          uint position = 0;
886          char namePart[MAX_LOCATION];
887
888          strcpy(namePart, name);
889          if(!strcmp(namePart, "/") || !strcmp(namePart, "\\"))
890             strcpy(namePart, DIR_SEPS);
891
892          position = archive.Find(this, name, entry);
893          if(position)
894          {
895             // Unlink from old directory
896             if(entry.prev)
897             {
898                archive.f.Seek(entry.prev + OFFSET(EAREntry, next), start);
899                archive.f.Write(&entry.next, sizeof(uint), 1);
900             }
901             if(entry.next)
902             {
903                archive.f.Seek(entry.next + OFFSET(EAREntry, prev), start);
904                archive.f.Write(&entry.prev, sizeof(uint), 1);
905             }
906             if(last == position) last = entry.prev;
907             if(first == position) first = entry.next;
908
909             // Relink to new directory
910             entry.prev = to.last;
911             entry.next = 0;
912
913             if(entry.prev)
914             {
915                archive.f.Seek(entry.prev + OFFSET(EAREntry, next), start);
916                archive.f.Write(&position, sizeof(uint), 1);
917             }
918             if(!to.first)
919                to.first = position;
920             to.last = position;
921
922             archive.f.Seek(position + OFFSET(EAREntry, prev), start);
923             archive.f.Write(&entry.prev, sizeof(uint), 1);
924             archive.f.Write(&entry.next, sizeof(uint), 1);
925
926             result = true;
927          }
928       }
929       return result;
930    }
931
932    bool Rename(const char * name, const char * newName)
933    {
934       bool result = false;
935       EAREntry entry { };
936       uint position = 0;
937       char namePart[MAX_LOCATION];
938
939       strcpy(namePart, name);
940       if(!strcmp(namePart, "/") || !strcmp(namePart, "\\"))
941          strcpy(namePart, DIR_SEPS);
942
943       position = archive.Find(this, namePart, entry);
944       if(position)
945       {
946          uint dataSize;
947          EAREntry newEntry = entry;
948          uint newPosition = position;
949
950          if(entry.type == ENTRY_FOLDER)
951             dataSize = 2 * sizeof(uint);
952          else
953             dataSize = entry.cSize ? entry.cSize : entry.size;
954
955          newEntry.nameLen = strlen(newName);
956          if(newEntry.nameLen > entry.nameLen)
957          {
958             // Write new entry
959             newPosition = archive.Position(sizeof(EAREntry) + newEntry.nameLen + dataSize);
960
961             archive.f.Seek(newPosition, start);
962             archive.f.Write(&newEntry, sizeof(EAREntry), 1);
963             archive.f.Write(newName, sizeof(char), newEntry.nameLen);
964
965             // Fix the links
966             if(entry.prev)
967             {
968                archive.f.Seek(entry.prev + OFFSET(EAREntry, next), start);
969                archive.f.Write(&newPosition, sizeof(uint), 1);
970             }
971             if(entry.next)
972             {
973                archive.f.Seek(entry.next + OFFSET(EAREntry, prev), start);
974                archive.f.Write(&newPosition, sizeof(uint), 1);
975             }
976             if(first == position) first = newPosition;
977             if(last == position) last = newPosition;
978          }
979          else
980          {
981             // Change the name
982             archive.f.Seek(position + OFFSET(EAREntry, nameLen), start);
983             archive.f.Write(&newEntry.nameLen, sizeof(uint), 1);
984             archive.f.Seek(position + sizeof(EAREntry), start);
985             archive.f.Write(newName, sizeof(char), newEntry.nameLen);
986
987             // There will be free space at the end of an entry with a shorter new name
988             if(newEntry.nameLen < entry.nameLen)
989                archive.AddFreeBlock(position + sizeof(EAREntry) + newEntry.nameLen + dataSize, entry.nameLen - newEntry.nameLen);
990          }
991          if(entry.nameLen != newEntry.nameLen)
992          {
993             byte * buffer;
994             uint bufferSize = Min(dataSize, MAX_BUFFERSIZE);
995             buffer = new byte[bufferSize];
996             if(buffer)
997             {
998                uint readPosition = position + sizeof(EAREntry) + entry.nameLen;
999                uint writePosition = newPosition + sizeof(EAREntry) + newEntry.nameLen;
1000                uint c;
1001
1002                for(c = 0; c<dataSize; c += bufferSize)
1003                {
1004                   uint size = (dataSize > c + bufferSize) ? bufferSize : (dataSize - c);
1005
1006                   archive.f.Seek(readPosition + c, start);
1007                   archive.f.Read(buffer, size, 1);
1008
1009                   archive.f.Seek(writePosition + c, start);
1010                   archive.f.Write(buffer, size, 1);
1011                }
1012                delete buffer;
1013             }
1014
1015             if(newEntry.nameLen > entry.nameLen)
1016             {
1017                // Prevent the children to be deleted
1018                if(entry.type == ENTRY_FOLDER)
1019                {
1020                   uint first = 0, last = 0;
1021                   archive.f.Seek(position + sizeof(EAREntry) + entry.nameLen, start);
1022                   archive.f.Write(&first, sizeof(uint), 1);
1023                   archive.f.Write(&last, sizeof(uint), 1);
1024                }
1025
1026                // Delete the old entry
1027                entry.prev = entry.next = 0;
1028                archive.f.Seek(position + sizeof(EAREntry) + entry.nameLen, start);
1029                archive.Delete(this, position, entry);
1030             }
1031          }
1032          result = true;
1033       }
1034       return result;
1035    }
1036
1037    bool AddFromFile(const char * name, File input, FileStats stats, ArchiveAddMode addMode, int compression, int * ratio, uint * newPosition)
1038    {
1039       // Search for identical entry
1040       EAREntry oldEntry;
1041       uint oldPosition = archive.Find(this, name, oldEntry);
1042       return _AddFromFileAtPosition(oldEntry, oldPosition, name, input, stats, addMode, compression, ratio, newPosition);
1043    }
1044
1045    bool AddFromFileAtPosition(uint oldPosition, const char * name, File input, FileStats stats, ArchiveAddMode addMode, int compression, int * ratio, uint * newPosition)
1046    {
1047       EAREntry oldEntry;
1048       if(oldPosition)
1049       {
1050          if(!archive.f.Seek(oldPosition, start) || !archive.f.Read(oldEntry, sizeof(EAREntry), 1))
1051             return false;
1052       }
1053       return _AddFromFileAtPosition(oldEntry, oldPosition, name, input, stats, addMode, compression, ratio, newPosition);
1054    }
1055
1056    bool _AddFromFileAtPosition(EAREntry oldEntry, uint oldPosition, const char * name, File input, FileStats stats, ArchiveAddMode addMode, int compression, int * ratio, uint * newPosition)
1057    {
1058       bool skip = false;
1059       FileStats oldStats { };
1060
1061       if(oldPosition)
1062       {
1063          oldStats.modified = (TimeStamp)oldEntry.modified;
1064          oldStats.created = (TimeStamp)oldEntry.created;
1065       }
1066       if(stats == null)
1067       {
1068          oldStats.size = input.GetSize();
1069          stats = &oldStats;
1070       }
1071
1072       switch(addMode)
1073       {
1074          // Add all files
1075          case replace:
1076             if(oldPosition)
1077                archive.Delete(this, oldPosition, oldEntry);
1078             break;
1079          // Only updates changed files
1080          case refresh:
1081             if(oldPosition &&
1082                  (oldEntry.size != stats.size ||
1083                   oldEntry.modified != (TimeStamp32)stats.modified ||
1084                   oldEntry.created != (TimeStamp32)stats.created))
1085                   archive.Delete(this, oldPosition, oldEntry);
1086             else
1087                skip = true;
1088             break;
1089          // Only updates changed or new files
1090          case update:
1091             if(oldPosition)
1092             {
1093                if(oldEntry.size != stats.size ||
1094                   oldEntry.modified != (TimeStamp32)stats.modified ||
1095                   oldEntry.created != (TimeStamp32)stats.created)
1096                   archive.Delete(this, oldPosition, oldEntry);
1097                else
1098                   skip = true;
1099             }
1100             break;
1101       }
1102
1103       if(!skip)
1104       {
1105          EAREntry entry { };
1106          uint position, size;
1107          byte * compressed = null;
1108
1109          // Add the file
1110          entry.nameLen = strlen(name);
1111          entry.prev = last;
1112          entry.next = 0;
1113          entry.type = ENTRY_FILE;
1114
1115          entry.size = stats.size;
1116          entry.created = (TimeStamp32)stats.created;
1117          entry.modified = (TimeStamp32)stats.modified;
1118
1119          if(compression)
1120          {
1121             byte * uncompressed = new byte[entry.size];
1122             if(uncompressed)
1123             {
1124                if(input.Read(uncompressed, 1, entry.size) == entry.size)
1125                {
1126                   unsigned long destLen = entry.size + entry.size / 1000 + 12;
1127
1128                   compressed = new byte[destLen];
1129                   if(compressed)
1130                   {
1131                      if(compression > 9 || compression < 0) compression = 9;
1132                      compress2(compressed, &destLen, uncompressed, entry.size, compression);
1133                      entry.cSize = (FileSize)destLen;
1134                   }
1135                }
1136                delete uncompressed;
1137             }
1138          }
1139
1140          if(!compressed)
1141          {
1142             entry.cSize = 0;
1143             if(ratio)
1144                *ratio = 0;
1145          }
1146          else if(ratio)
1147             *ratio = entry.size ? (entry.cSize * 1000 / entry.size) : 0;
1148
1149          // Find block
1150          size = sizeof(EAREntry) + entry.nameLen + (entry.cSize ? entry.cSize : entry.size);
1151          position = archive.Position(size);
1152
1153          // Write Header
1154          if(!archive.f.Seek(position, start) ||
1155             !archive.f.Write(entry, sizeof(EAREntry), 1) ||
1156             !archive.f.Write(name, entry.nameLen, 1))
1157          {
1158             delete compressed;
1159             return false;
1160          }
1161
1162          // Write File Data
1163          if(compressed)
1164          {
1165             if(!archive.f.Write(compressed, 1, entry.cSize))
1166             {
1167                delete compressed;
1168                return false;
1169             }
1170             delete compressed;
1171          }
1172          else
1173          {
1174             byte buffer[8192];
1175             uint c;
1176             int count = 1;
1177             for(c = 0; c<entry.size && count; c+= count)
1178             {
1179                count = input.Read(buffer, 1, sizeof(buffer));
1180                if(!archive.f.Write(buffer, 1, count))
1181                   return false;
1182             }
1183          }
1184
1185          // Update the next pointer previous entry
1186          if(entry.prev)
1187          {
1188             archive.f.Seek(entry.prev + OFFSET(EAREntry, next), start);
1189             archive.f.Write(&position, sizeof(uint), 1);
1190          }
1191
1192          // Update total size of archive
1193          archive.totalSize += entry.size;
1194
1195          last = position;
1196          if(!first) first = position;
1197          if(newPosition) *newPosition = position;
1198       }
1199       else
1200       {
1201          if(newPosition) *newPosition = 0;
1202       }
1203
1204       // archive.f.handle = archive.f;
1205       return true;
1206    }
1207 };
1208 #endif // !defined(ECERE_NOARCHIVE) && !defined(ECERE_VANILLA)
1209
1210 // Directory Description for file listing
1211 class EARDir : struct
1212 {
1213    char path[MAX_LOCATION];
1214    File f;
1215    uint next;
1216 };
1217
1218 class EARFile : File
1219 {
1220    uint position;
1221    uint size;
1222
1223    // -- For reading compressed file (entirely buffered)
1224    byte * buffer;
1225
1226    // -- For reading uncompressed file (not buffered)
1227    File f;
1228    uint start;
1229
1230    ~EARFile()
1231    {
1232       delete buffer;
1233       delete f;
1234    }
1235
1236    void CloseInput()
1237    {
1238       if(f)
1239          f.CloseInput();
1240    }
1241
1242    void CloseOutput()
1243    {
1244       if(f)
1245          f.CloseOutput();
1246    }
1247
1248    int Read(byte * buffer, uint size, uint count)
1249    {
1250       int read = 0;
1251       if(f)
1252          f.Seek(position + start, start);
1253       read = Min(count, (this.size - position) / size);
1254       if(this.buffer)
1255          CopyBytes(buffer, this.buffer + position, read * size);
1256       else
1257          read = f.Read(buffer, size, read);
1258       position += read * size;
1259       return read;
1260    }
1261
1262    int Write(const byte * buffer, uint size, uint count)
1263    {
1264       return 0;
1265    }
1266
1267    bool Getc(char * ch)
1268    {
1269       if(position < size)
1270       {
1271          if(buffer)
1272          {
1273             char b = buffer[position++];
1274             if(ch) *ch = b;
1275             return true;
1276          }
1277          else
1278          {
1279             f.Seek(position + start, start);
1280             position++;
1281             return f.Getc(ch);
1282          }
1283       }
1284       return false;
1285    }
1286
1287    bool Putc(char ch)
1288    {
1289       return false;
1290    }
1291
1292    bool Puts(const char * string)
1293    {
1294       return false;
1295    }
1296
1297    bool Seek(int pos, FileSeekMode mode)
1298    {
1299       bool result = false;
1300       switch(mode)
1301       {
1302          case start:
1303             if(pos <= (int)size)
1304             {
1305                position = pos;
1306                if(f)
1307                   result = f.Seek(position + start, start);
1308                else
1309                   result = true;
1310             }
1311             break;
1312          case current:
1313             if(position + pos <= (int)size && (int)position >= -pos)
1314             {
1315                position += pos;
1316                if(f)
1317                   result = f.Seek(position + start, start);
1318                else
1319                   result = true;
1320             }
1321             break;
1322          case end:
1323             if(pos < 0 && -pos <= (int)size)
1324             {
1325                position = size + pos;
1326                if(f)
1327                   f.Seek(position + start, start);
1328                else
1329                   result = true;
1330             }
1331             break;
1332       }
1333       return result;
1334    }
1335
1336    uint Tell()
1337    {
1338       return position;
1339    }
1340
1341    bool Eof()
1342    {
1343       return position >= size || (f && f.Eof());
1344    }
1345
1346    uint GetSize()
1347    {
1348       return size;
1349    }
1350 };
1351
1352 class EARFileSystem : FileSystem
1353 {
1354    File ::Open(const char * archive, const char * name, FileOpenMode mode)
1355    {
1356       File result = null;
1357       if(mode == read)
1358       {
1359          EARFile file {};
1360          if(file)
1361          {
1362             char fileName[MAX_LOCATION];
1363             EARHeader header;
1364             File f = EAROpenArchive(archive, &header);
1365             strcpy(fileName, name);
1366    #ifdef ECERE_STATIC
1367             if(!f && archive[0] == ':')
1368             {
1369                f = EAROpenArchive(":", &header);
1370                if(f)
1371                {
1372                   strcpy(fileName, archive + 1);
1373                   PathCat(fileName, name);
1374                }
1375             }
1376    #endif
1377             if(f)
1378             {
1379                EAREntry entry { };
1380                if(EARGetEntry(f, entry, fileName, null).isFile)
1381                {
1382                   if(entry.cSize)
1383                   {
1384                      byte * uncompressed = new byte[entry.size];
1385                      if(uncompressed)
1386                      {
1387                         byte * compressed = new byte[entry.cSize];
1388                         if(compressed)
1389                         {
1390                            if(f.Read(compressed, 1, entry.cSize) == entry.cSize)
1391                            {
1392                               unsigned long destLen = entry.size;
1393                               uncompress(uncompressed, &destLen, compressed, entry.cSize);
1394                               entry.size = (FileSize)destLen;
1395                            }
1396                            delete compressed;
1397                         }
1398
1399                         file.position = 0;
1400                         file.size = entry.size;
1401                         file.buffer = uncompressed;
1402
1403                         result = file;
1404                      }
1405                   }
1406                   else
1407                   {
1408                      file.start = f.Tell();
1409                      file.position = 0;
1410                      file.size = entry.size;
1411                      file.f = f;
1412                      f = null;
1413
1414                      result = file;
1415                   }
1416                }
1417                delete f;
1418             }
1419             if(!result)
1420                delete file;
1421          }
1422       }
1423       return result;
1424    }
1425
1426    FileAttribs ::Exists(const char * archive, const char * fileName)
1427    {
1428       uint result = 0;
1429       EARHeader header;
1430       File f = EAROpenArchive(archive, &header);
1431       if(f)
1432       {
1433          EAREntry entry { };
1434          result = EARGetEntry(f, entry, fileName, null);
1435          delete f;
1436       }
1437    #ifdef ECERE_STATIC
1438       if(!f && archive[0] == ':')
1439       {
1440          f = EAROpenArchive(":", &header);
1441          if(f)
1442          {
1443             EAREntry entry { };
1444             char fn[MAX_LOCATION];
1445             strcpy(fn, archive + 1);
1446             PathCat(fn, fileName);
1447             result = EARGetEntry(f, entry, fn, null);
1448          }
1449          delete f;
1450       }
1451    #endif
1452       return result;
1453    }
1454
1455    bool ::GetSize(const char * archive, const char * fileName, FileSize * size)
1456    {
1457       bool result = false;
1458       EARHeader header;
1459       File f = EAROpenArchive(archive, &header);
1460       if(f)
1461       {
1462          EAREntry entry { };
1463          if(EARGetEntry(f, entry, fileName, null))
1464             *size = entry.size;
1465          delete f;
1466          result = true;
1467       }
1468       return result;
1469    }
1470
1471    bool ::Stats(const char * archive, const char * fileName, FileStats stats)
1472    {
1473       bool result = false;
1474       EARHeader header;
1475       File f = EAROpenArchive(archive, &header);
1476       if(f)
1477       {
1478          EAREntry entry { };
1479          if(EARGetEntry(f, entry, fileName, null))
1480          {
1481             stats.size = entry.size;
1482             stats.accessed = 0;
1483             stats.modified = (TimeStamp)entry.modified;
1484             stats.created = (TimeStamp)entry.created;
1485             result = true;
1486          }
1487          delete f;
1488       }
1489       return result;
1490    }
1491
1492    void ::FixCase(const char * archive, char * name)
1493    {
1494    #ifdef __WIN32__
1495       EARHeader header;
1496       File f = EAROpenArchive(archive, &header);
1497       if(f)
1498       {
1499          EAREntry entry { };
1500          char fileName[MAX_LOCATION] = "";
1501          if(EARGetEntry(f, entry, name, fileName))
1502             strcpy(name, fileName);
1503          delete f;
1504       }
1505    #endif
1506    }
1507
1508    bool ::Find(FileDesc file, const char * archive, const char * name)
1509    {
1510       bool result = false;
1511       EARDir d {};
1512       if(d)
1513       {
1514          EARHeader header;
1515          File f = EAROpenArchive(archive, &header);
1516          if(f)
1517          {
1518             EAREntry entry { };
1519             if(EARGetEntry(f, entry, name, null).isDirectory)
1520             {
1521                uint first, last;
1522
1523                sprintf(d.path, "<%s>%s", archive, name);
1524                d.f = f;
1525                f.Read(&first, sizeof(uint), 1);
1526                f.Read(&last, sizeof(uint), 1);
1527                d.next = first;
1528                if(d.next)
1529                {
1530                   EAREntry entry { };
1531                   d.f.Seek(d.next, start);
1532                   d.f.Read(entry, sizeof(EAREntry), 1);
1533                   d.f.Read(file.name, 1, entry.nameLen);
1534                   file.name[entry.nameLen] = '\0';
1535                   file.stats.attribs = { isDirectory = (entry.type == ENTRY_FOLDER), isFile = (entry.type != ENTRY_FOLDER) };
1536                   file.stats.accessed = file.stats.modified = (TimeStamp)entry.modified;
1537                   file.stats.created = (TimeStamp)entry.created;
1538                   file.stats.size = entry.size;
1539
1540                   strcpy(file.path, d.path);
1541                   PathCat(file.path, file.name);
1542                   d.next = entry.next;
1543
1544                   file.dir = (Dir)d;
1545
1546                   result = true;
1547                }
1548             }
1549             if(!result)
1550                delete f;
1551          }
1552          if(!result)
1553             delete d;
1554       }
1555       return result;
1556    }
1557
1558    bool ::FindNext(FileDesc file)
1559    {
1560       bool result = false;
1561       EARDir d = (EARDir)file.dir;
1562       if(d.next)
1563       {
1564          EAREntry entry { };
1565          d.f.Seek(d.next, start);
1566          d.f.Read(entry, sizeof(EAREntry), 1);
1567          d.f.Read(file.name, 1, entry.nameLen);
1568          file.name[entry.nameLen] = '\0';
1569          file.stats.attribs = FileAttribs { isDirectory = (entry.type == ENTRY_FOLDER), isFile = (entry.type != ENTRY_FOLDER) };
1570          file.stats.accessed = file.stats.modified = (TimeStamp)entry.modified;
1571          file.stats.created = (TimeStamp)entry.created;
1572          file.stats.size = entry.size;
1573
1574          strcpy(file.path, d.path);
1575          PathCat(file.path, file.name);
1576          d.next = entry.next;
1577
1578          result = true;
1579       }
1580       return result;
1581    }
1582
1583    void ::CloseDir(FileDesc file)
1584    {
1585       EARDir d = (EARDir)file.dir;
1586       if(d.f)
1587          delete d.f;
1588       if(d)
1589          delete d;
1590    }
1591
1592 #if !defined(ECERE_NOARCHIVE) && !defined(ECERE_VANILLA)
1593    Archive ::OpenArchive(const char * fileName, ArchiveOpenFlags flags)
1594    {
1595       Archive result = null;
1596       EARArchive archive { writeAccess = flags.writeAccess };
1597       if(archive)
1598       {
1599          int try = flags.waitLock ? 10 : 0;
1600          for(; try >= 0; try--)
1601          {
1602             // Check for existing Archive
1603             if((archive.f = fileName ? (flags.buffered ? FileOpenBuffered : FileOpen)(fileName, flags.writeAccess ? readWrite : read) : TempFile { openMode = readWrite } ))
1604             {
1605                EARHeader header;
1606                bool opened = false;
1607                uint archiveSize = 0;
1608                archive.f.Seek(-(int)sizeof(uint), end);
1609                archive.f.Read(&archiveSize, sizeof(uint), 1);
1610                archive.f.Seek(-(int)archiveSize, end);
1611
1612                archive.archiveStart = archive.f.Tell();
1613                if(archive.f.Read(&header, sizeof(EARHeader), 1) == 1 &&
1614                   !memcmp(header.recognition, earRecognition, sizeof(earRecognition)))
1615                   opened = true;
1616
1617                if(!opened)
1618                {
1619                   archive.f.Seek(0, start);
1620                   archive.archiveStart = archive.f.Tell();
1621                   archiveSize = archive.f.GetSize();
1622                   if(archive.f.Read(&header, sizeof(EARHeader), 1) == 1 &&
1623                      !memcmp(header.recognition, earRecognition, sizeof(earRecognition)))
1624                      opened = true;
1625                }
1626
1627                if(opened)
1628                {
1629                   // At this point we recognized the file as a valid eAR archive
1630                   archive.rootDir = archive.archiveStart + sizeof(EARHeader);
1631                   archive.totalSize = header.totalSize;
1632
1633                   archive.f.Seek(archive.rootDir, start);
1634                   if(flags.writeAccess)
1635                   {
1636                      if(flags.buffered)
1637                      {
1638                         archive.freeBlocks.Add(FreeBlock { start = archive.rootDir + 2 * sizeof(uint), end = MAXDWORD });
1639                         archive.SubtractUsedBlocks();
1640                      }
1641                      else
1642                      {
1643                         archive.freeBlocks.Add(FreeBlock { start = archive.archiveStart + (archiveSize - sizeof(uint)), end = MAXDWORD });
1644                      }
1645                   }
1646
1647                   /*
1648                   if(!flags.writeAccess)
1649                   {
1650                      delete archive.f;
1651                      archive.f = FileOpen(fileName, readWrite);
1652                   }
1653                   */
1654                   if(archive.f)
1655                   {
1656                      incref archive.f;
1657                      result = archive;
1658                   }
1659                }
1660                break;
1661             }
1662             else if(try > 0)
1663                Sleep(0.01);
1664          }
1665
1666          // This piece of code will create a new archive as a new file or at the footer
1667          // of an existing file.
1668          if(!result && flags.writeAccess)
1669          {
1670             // If the file doesn't exist, create it
1671             if(!archive.f)
1672             {
1673                archive.f = FileOpen(fileName, writeRead);
1674                delete archive.f;
1675                archive.f = (flags.buffered ? FileOpenBuffered : FileOpen)(fileName, readWrite);
1676             }
1677             if(archive.f)
1678             {
1679                EARHeader header
1680                {
1681                   EAR_RECOGNITION,
1682                   MDWORD(0, 1)
1683                };
1684
1685                archive.f.Seek(0, end);
1686
1687                archive.archiveStart = archive.f.Tell();
1688                archive.freeBlocks.Add(FreeBlock { start = archive.archiveStart + sizeof(EARHeader), end = MAXDWORD });
1689
1690                // Write Header
1691                archive.f.Write(&header, sizeof(EARHeader), 1);
1692                {
1693                   uint first = 0, last = 0;
1694                   archive.f.Write(&first, sizeof(first), 1);
1695                   archive.f.Write(&last, sizeof(last), 1);
1696                }
1697
1698                archive.rootDir = 0;
1699                incref archive.f;
1700                result = archive;
1701             }
1702          }
1703          if(archive.f && flags.writeAccess && flags.exclusive && !archive.f.Lock(flags.exclusive ? exclusive : shared, 0, 0, flags.waitLock))
1704             result = null;
1705          if(!result)
1706          {
1707             delete archive.f;
1708             delete archive;
1709          }
1710          else
1711          {
1712             // archive.f.handle = archive.f;
1713          }
1714       }
1715       return result;
1716    }
1717 #endif // !defined(ECERE_NOARCHIVE) && !defined(ECERE_VANILLA)
1718    bool ::QuerySize(const char * archive, FileSize * size)
1719    {
1720       bool result = false;
1721       EARHeader header;
1722       File f = EAROpenArchive(archive, &header);
1723       if(f)
1724       {
1725          *size = header.totalSize;
1726          result = true;
1727          delete f;
1728       }
1729       return result;
1730    }
1731 };