namespace sys; #define uint _uint #include "zlib.h" #undef uint import "System" import "BufferedFile" #define OFFSET(s, m) ((uint)(uintptr) (&((s *) 0)->m)) #define MDWORD(a,b) ((((uint32)((uint16)(b)))<<16)+((uint16)(a))) #define EAR_RECOGNITION { 'e', 'A', 'R', 228, 11, 12, 3, 0 } static byte earRecognition[] = EAR_RECOGNITION; static class FreeBlock : struct { FreeBlock prev, next; uint start, end; }; static struct EARHeader { byte recognition[sizeof(earRecognition)]; uint version __attribute__((packed)); FileSize totalSize __attribute__((packed)); }; static enum EAREntryType { ENTRY_FILE = 1, ENTRY_FOLDER = 2 }; static struct EAREntry { EAREntryType type __attribute__((packed)); TimeStamp32 created, modified __attribute__((packed)); FileSize size, cSize __attribute__((packed)); uint prev, next __attribute__((packed)); uint nameLen __attribute__((packed)); // null terminated file name follows }; static File EAROpenArchive(const char * archive, EARHeader header) { File f = null; if(archive[0] == ':') { char moduleName[MAX_LOCATION]; const char * name = archive + 1; #if defined(__ANDROID__) if(!name[0]) name = ((SubModule)__thisModule.application.modules.first).next.module.name; #endif if(LocateModule(name, moduleName)) f = FileOpen(moduleName, read); } else f = FileOpen(archive, read); if(f) { uint archiveSize; // First attempt to treat this as an archive file if(f.Read(header, sizeof(EARHeader), 1) == 1 && !memcmp(header.recognition, earRecognition, sizeof(earRecognition))) return f; // Then try to see if an archive is at the end of the file f.Seek(-(int)sizeof(uint), end); f.Read(&archiveSize, sizeof(uint), 1); f.Seek(-(int)archiveSize, end); if(f.Read(header, sizeof(EARHeader), 1) == 1 && !memcmp(header.recognition, earRecognition, sizeof(earRecognition))) return f; delete f; } return null; } static FileAttribs EARGetEntry(File f, EAREntry entry, const char * name, char * path) { uint first = 0, last = 0; if(!name[0]) return FileAttribs { isDirectory = true }; if(!f.Read(&first, sizeof(uint), 1)) return 0; f.Read(&last, sizeof(uint), 1); if(first) { char namePart[MAX_FILENAME], nameRest[MAX_LOCATION]; SplitDirectory(name, namePart, nameRest); if(!strcmp(namePart, "/") || !strcmp(namePart, "\\")) strcpy(namePart, DIR_SEPS); f.Seek(first, start); for(;;) { char fileName[MAX_FILENAME]; f.Read(entry, sizeof(EAREntry), 1); f.Read(fileName, 1, entry.nameLen); fileName[entry.nameLen] = '\0'; if(!strcmp(fileName, "/") || !strcmp(fileName, "\\")) strcpy(fileName, DIR_SEPS); if(!fstrcmp(fileName, namePart)) { if(path) PathCat(path, fileName); if(nameRest[0]) return EARGetEntry(f, entry, nameRest, path); else return (entry.type == ENTRY_FILE) ? FileAttribs { isFile = true } : FileAttribs { isDirectory = true }; } if(entry.next) f.Seek(entry.next, start); else break; } } return FileAttribs { }; } #if !defined(ECERE_NOARCHIVE) && !defined(ECERE_VANILLA) class EARArchive : Archive { File f; //BufferedFile bf { }; // char path[MAX_LOCATION]; uint archiveStart; uint rootDir; OldList freeBlocks; bool writeAccess; uint Update() { if(rootDir) { uint end; end = ((FreeBlock)freeBlocks.last).start; // Update header f.Seek(archiveStart + OFFSET(EARHeader, totalSize), start); f.Write(&totalSize, sizeof(uint), 1); // Write Footer f.Seek(end, start); end += sizeof(uint); end -= archiveStart; f.Write(&end, sizeof(uint), 1); f.Truncate(archiveStart + end); return end; } return 0; } ~EARArchive() { if(f && rootDir && writeAccess) { // Perform Defrag Defrag(rootDir); archiveStart += Update(); } if(f && writeAccess) { f.Flush(); f.Unlock(0, 0, true); } delete f; /*if(rootDir && writeAccess) { // Fix the size of the archive FileTruncate(path, archiveStart); }*/ freeBlocks.Free(null); } bool Clear() { rootDir = 0; return true; } ArchiveDir OpenDirectory(const char * name, FileStats stats, ArchiveAddMode addMode) { ArchiveDir result = null; EARArchiveDir dir { readOnly = addMode == readOnlyDir }; if(dir) { char namePart[MAX_LOCATION] = "", nameRest[MAX_LOCATION]; dir.archive = this; strcpy(nameRest, name); if(!strcmp(namePart, "/") || !strcmp(namePart, "\\")) strcpy(namePart, DIR_SEPS); // Search for directory if(rootDir) { dir.position = rootDir; if(f.Seek(dir.position, start)) { dir.first = 0; dir.last = 0; f.Read(&dir.first, sizeof(uint), 1); f.Read(&dir.last, sizeof(uint), 1); result = dir; } } // If directory doesn't exist already if(!result && addMode != refresh) { rootDir = Position(2*sizeof(uint)); dir.position = rootDir; } result = dir; // Open rest of directory... if(result && nameRest[0]) { result = dir.OpenDirectory(nameRest, stats, addMode); delete dir; } } return result; } uint Position(uint size) { FreeBlock block; for(block = freeBlocks.first; block; block = block.next) { if(block.end - block.start + 1 >= size) { uint position = block.start; if(block.end - block.start + 1 == size) freeBlocks.Delete(block); else block.start += size; return position; } } return 0; } bool DefragOffset(uint * offset) { FreeBlock block; uint subtract = 0; for(block = freeBlocks.first; block; block = block.next) { if(*offset > block.start) subtract += block.end - block.start + 1; else break; } if(subtract) { *offset -= subtract; return true; } return false; } #define MAX_BUFFERSIZE 0x400000 void Defrag(uint dirPosition) { // Update all offsets within the files uint first = 0, last = 0; uint position = 0, next = 0; f.Seek(dirPosition, start); f.Read(&first, sizeof(uint), 1); f.Read(&last, sizeof(uint), 1); position = first; if(first && DefragOffset(&first)) { if(f.Seek(dirPosition, start)) f.Write(&first, sizeof(uint), 1); } if(last && DefragOffset(&last)) { if(f.Seek(dirPosition + sizeof(uint), start)) f.Write(&last, sizeof(uint), 1); } for(; position; position = next) { EAREntry entry { }; if(f.Seek(position, start) && f.Read(entry, sizeof(EAREntry), 1)) { next = entry.next; if(entry.prev && DefragOffset(&entry.prev)) { f.Seek(position + OFFSET(EAREntry, prev), start); f.Write(&entry.prev, sizeof(uint), 1); } if(entry.next && DefragOffset(&entry.next)) { f.Seek(position + OFFSET(EAREntry, next), start); f.Write(&entry.next, sizeof(uint), 1); } if(entry.type == ENTRY_FOLDER) Defrag(position + sizeof(EAREntry) + entry.nameLen); } else return; } // Move all the blocks if(dirPosition == rootDir) { uint bufferSize = 0; byte * buffer = null; FreeBlock block, nextBlock; for(block = freeBlocks.first; block && block.next; block = nextBlock) { uint dataSize, c; nextBlock = block.next; dataSize = nextBlock.start - (block.end + 1); if(dataSize > bufferSize && (!bufferSize || bufferSize < MAX_BUFFERSIZE)) { bufferSize = Min(dataSize, MAX_BUFFERSIZE); buffer = renew buffer byte[bufferSize]; } for(c = 0; c c + bufferSize) ? bufferSize : (dataSize - c); // Read block of data f.Seek((block.end + 1) + c, start); f.Read(buffer, size, 1); // Write block of data f.Seek(block.start + c, start); f.Write(buffer, size, 1); } nextBlock.start -= (block.end - block.start) + 1; freeBlocks.Delete(block); } delete buffer; } } uint Find(EARArchiveDir directory, const char * namePart, EAREntry entry) { uint position; for(position = directory.first; position; position = entry.next) { char fileName[MAX_FILENAME]; if(f.Seek(position, start) && f.Read(entry, sizeof(EAREntry), 1)) { if(entry.nameLen > MAX_FILENAME) return 0; // CORRUPTION ERROR f.Read(fileName, 1, entry.nameLen); fileName[entry.nameLen] = '\0'; if(!strcmp(fileName, "/") || !strcmp(fileName, "\\")) strcpy(fileName, DIR_SEPS); if(!fstrcmp(fileName, namePart)) return position; } else return 0; // ERROR OUT OF SPACE? } return 0; } void AddFreeBlock(uint position, uint size) { FreeBlock block, prevBlock, nextBlock = null; // Find the previous and next free block prevBlock = null; for(block = freeBlocks.first; block; block = block.next) if(block.end < position) prevBlock = block; else { nextBlock = block; break; } // Try to merge with previous block if(prevBlock && prevBlock.end + 1 == position) { prevBlock.end += size; // Try to merge with next block as well if(nextBlock && nextBlock.start == prevBlock.end + 1) { prevBlock.end = nextBlock.end; freeBlocks.Delete(nextBlock); } } // Try to merge with next block else if(nextBlock && nextBlock.start == position + size) { nextBlock.start = position; } // This free block is not connected to any other block else { freeBlocks.Insert(prevBlock, FreeBlock { start = position, end = position + size - 1 }); } } void SubtractBlock(uint start, uint size) { FreeBlock block; for(block = freeBlocks.first; block; block = block.next) { if(block.end >= start - 1L && block.start <= start + size) { if(block.end > start + size && block.start < start - 1L) { FreeBlock newBlock { start = start + size, end = block.end }; block.end = start - 1L; freeBlocks.Insert(block, newBlock); } else if(block.end > start + size) { block.start = start + size; } else if(block.start < start - 1L) { block.end = start - 1L; } else { freeBlocks.Remove(block); delete block; } break; } } } void Delete(EARArchiveDir dir, uint position, EAREntry entry) { uint size; if(entry.type == ENTRY_FOLDER) { EARArchiveDir subDir {}; uint filePosition; EAREntry fileEntry; subDir.position = dir.position; f.Read(&subDir.first, sizeof(uint), 1); f.Read(&subDir.last, sizeof(uint), 1); // Erase directory contents first for(filePosition = subDir.first; filePosition; filePosition = fileEntry.next) { f.Seek(filePosition, start); f.Read(&fileEntry, sizeof(EAREntry), 1); f.Seek(fileEntry.nameLen, current); Delete(subDir, filePosition, &fileEntry); } size = sizeof(EAREntry) + entry.nameLen + 2 * sizeof(uint); delete subDir; } else size = sizeof(EAREntry) + entry.nameLen + (entry.cSize ? entry.cSize : entry.size); // Unlink this file if(entry.prev) { f.Seek(entry.prev + OFFSET(EAREntry, next), start); f.Write(&entry.next, sizeof(uint), 1); } if(entry.next) { f.Seek(entry.next + OFFSET(EAREntry, prev), start); f.Write(&entry.prev, sizeof(uint), 1); } if(dir.last == position) dir.last = entry.prev; if(dir.first == position) dir.first = entry.next; AddFreeBlock(position, size); totalSize -= entry.size; // Invalidate Buffer // bf.handle = f; } File FileOpen(const char * name) { File result = null; EARFile file {}; if(file) { EAREntry entry { }; f.Seek(archiveStart + sizeof(EARHeader), start); if(EARGetEntry(f, entry, name, null).isFile) { if(entry.cSize) { byte * uncompressed = new byte[entry.size]; if(uncompressed) { byte * compressed = new byte[entry.cSize]; if(compressed) { if(f.Read(compressed, 1, entry.cSize) == entry.cSize) { unsigned long destLen = entry.size; uncompress(uncompressed, &destLen, compressed, entry.cSize); entry.size = (FileSize)destLen; // TODO: Support 64 bit file sizes } delete compressed; } file.position = 0; file.size = entry.size; file.buffer = uncompressed; result = file; } } else { file.start = f.Tell(); file.position = 0; file.size = entry.size; file.f = f; incref file.f; file.f.Seek(file.start, start); result = file; } } if(!result) delete file; } return result; } File FileOpenAtPosition(uint position) { EARFile file {}; char fileName[MAX_LOCATION]; EAREntry entry { }; f.Seek(position, start); f.Read(entry, sizeof(EAREntry), 1); /*if(entry.nameLen > 1024) printf("");*/ f.Read(fileName, 1, entry.nameLen); if(entry.cSize) { byte * uncompressed = new byte[entry.size]; if(uncompressed) { byte * compressed = new byte[entry.cSize]; if(compressed) { if(f.Read(compressed, 1, entry.cSize) == entry.cSize) { unsigned long destLen = entry.size; uncompress(uncompressed, &destLen, compressed, entry.cSize); entry.size = (FileSize)destLen; } delete compressed; } file.position = 0; file.size = entry.size; file.buffer = uncompressed; } } else { file.start = f.Tell(); file.position = 0; file.size = entry.size; file.f = f; file.f.Seek(file.start, start); incref file.f; } return file; } FileAttribs FileExists(const char * fileName) { FileAttribs result; EAREntry entry { }; f.Seek(archiveStart + sizeof(EARHeader), start); result = EARGetEntry(f, entry, fileName, null); return result; } void SubtractUsedBlocks() { uint first, last; if(!f.Read(&first, sizeof(uint), 1)) return; #ifdef _DEBUG if(first > f.GetSize()) { printf("Error\n"); } #endif f.Read(&last, sizeof(uint), 1); while(first) { uint size = 0; char fileName[MAX_FILENAME]; EAREntry entry { }; f.Seek(first, start); f.Read(entry, sizeof(EAREntry), 1); if(entry.nameLen < MAX_FILENAME) { f.Read(fileName, 1, entry.nameLen); fileName[entry.nameLen] = 0; } else { fileName[0] = 0; break; } size += sizeof(EAREntry) + entry.nameLen; if(entry.type == ENTRY_FILE) { size += entry.cSize ? entry.cSize : entry.size; } else if(entry.type == ENTRY_FOLDER) { size += 2 * sizeof(uint); SubtractUsedBlocks(); } SubtractBlock(first, size); first = entry.next; } } void SetBufferSize(uint bufferSize) { if(f && f._class == class(BufferedFile)) ((BufferedFile)f).bufferSize = bufferSize; } void SetBufferRead(uint bufferRead) { if(f && f._class == class(BufferedFile)) ((BufferedFile)f).bufferRead = bufferRead; } } class EARArchiveDir : ArchiveDir { EARArchive archive; uint position; uint first, last; bool readOnly; ~EARArchiveDir() { if(!readOnly) { archive.f.Seek(position, start); archive.f.Write(&first, sizeof(uint), 1); archive.f.Write(&last, sizeof(uint), 1); archive.Update(); } } File FileOpen(const char * name) { File result = null; EARFile file {}; if(file) { EAREntry entry { }; archive.f.Seek(position, start); if(EARGetEntry(archive.f, entry, name, null).isFile) { if(entry.cSize) { byte * uncompressed = new byte[entry.size]; if(uncompressed) { byte * compressed = new byte[entry.cSize]; if(compressed) { if(archive.f.Read(compressed, 1, entry.cSize) == entry.cSize) { unsigned long destLen = entry.size; uncompress(uncompressed, &destLen, compressed, entry.cSize); entry.size = (FileSize)destLen; } delete compressed; } file.position = 0; file.size = entry.size; file.buffer = uncompressed; result = file; } } else { file.start = archive.f.Tell(); file.position = 0; file.size = entry.size; file.f = archive.f; file.f.Seek(file.start, start); incref file.f; result = file; } } if(!result) delete file; } return result; } FileAttribs FileExists(const char * fileName) { FileAttribs result; EAREntry entry { }; archive.f.Seek(position, start); result = EARGetEntry(archive.f, entry, fileName, null); return result; } ArchiveDir OpenDirectory(const char * name, FileStats stats, ArchiveAddMode addMode) { ArchiveDir result = null; EARArchiveDir dir { readOnly = addMode == readOnlyDir }; if(dir) { char namePart[MAX_LOCATION] = "", nameRest[MAX_LOCATION]; uint position; EAREntry entry { }; dir.archive = archive; SplitDirectory(name, namePart, nameRest); if(!strcmp(namePart, "/") || !strcmp(namePart, "\\")) strcpy(namePart, DIR_SEPS); // Search for directory position = archive.Find(this, namePart, entry); if(position) { // Fail if file of same name already exists if(entry.type == ENTRY_FILE) return null; else { dir.position = position + sizeof(EAREntry) + entry.nameLen; dir.first = 0; dir.last = 0; archive.f.Read(&dir.first, sizeof(uint), 1); archive.f.Read(&dir.last, sizeof(uint), 1); result = dir; } } // If directory doesn't exist already if(!result && addMode != refresh) { // Write Header if it's not the root directory EAREntry entry {}; uint position; entry.nameLen = strlen(namePart); entry.prev = last; entry.next = 0; entry.type = ENTRY_FOLDER; if(!nameRest[0] && stats) { entry.created = (TimeStamp32)stats.created; entry.modified = (TimeStamp32)stats.modified; } position = archive.Position(sizeof(EAREntry) + entry.nameLen + 2*sizeof(uint)); archive.f.Seek(position, start); archive.f.Write(entry, sizeof(EAREntry), 1); archive.f.Write(namePart, entry.nameLen, 1); last = position; if(!first) first = position; // Update the next pointer of previous entry if(entry.prev) { archive.f.Seek(entry.prev + OFFSET(EAREntry, next), start); archive.f.Write(&position, sizeof(uint), 1); } // Make the dir position point after the header dir.position = position + sizeof(EAREntry) + entry.nameLen; } // Just update the time stamps else if(result && !nameRest[0] && stats) { archive.f.Seek(position + OFFSET(EAREntry, created), start); archive.f.Write(&stats.created, sizeof(uint), 1); archive.f.Write(&stats.modified, sizeof(uint), 1); } result = dir; // Open rest of directory... if(result && nameRest[0]) { result = dir.OpenDirectory(nameRest, stats, addMode); delete dir; } } return result; } bool Delete(const char * name) { EAREntry entry { }; uint position; char namePart[MAX_LOCATION]; strcpy(namePart, name); if(!strcmp(namePart, "/") || !strcmp(namePart, "\\")) strcpy(namePart, DIR_SEPS); position = archive.Find(this, namePart, entry); if(position) { archive.Delete(this, position, entry); return true; } return false; } bool Move(const char * name, EARArchiveDir to) { bool result = false; if(position != to.position) { EAREntry entry { }; uint position = 0; char namePart[MAX_LOCATION]; strcpy(namePart, name); if(!strcmp(namePart, "/") || !strcmp(namePart, "\\")) strcpy(namePart, DIR_SEPS); position = archive.Find(this, name, entry); if(position) { // Unlink from old directory if(entry.prev) { archive.f.Seek(entry.prev + OFFSET(EAREntry, next), start); archive.f.Write(&entry.next, sizeof(uint), 1); } if(entry.next) { archive.f.Seek(entry.next + OFFSET(EAREntry, prev), start); archive.f.Write(&entry.prev, sizeof(uint), 1); } if(last == position) last = entry.prev; if(first == position) first = entry.next; // Relink to new directory entry.prev = to.last; entry.next = 0; if(entry.prev) { archive.f.Seek(entry.prev + OFFSET(EAREntry, next), start); archive.f.Write(&position, sizeof(uint), 1); } if(!to.first) to.first = position; to.last = position; archive.f.Seek(position + OFFSET(EAREntry, prev), start); archive.f.Write(&entry.prev, sizeof(uint), 1); archive.f.Write(&entry.next, sizeof(uint), 1); result = true; } } return result; } bool Rename(const char * name, const char * newName) { bool result = false; EAREntry entry { }; uint position = 0; char namePart[MAX_LOCATION]; strcpy(namePart, name); if(!strcmp(namePart, "/") || !strcmp(namePart, "\\")) strcpy(namePart, DIR_SEPS); position = archive.Find(this, namePart, entry); if(position) { uint dataSize; EAREntry newEntry = entry; uint newPosition = position; if(entry.type == ENTRY_FOLDER) dataSize = 2 * sizeof(uint); else dataSize = entry.cSize ? entry.cSize : entry.size; newEntry.nameLen = strlen(newName); if(newEntry.nameLen > entry.nameLen) { // Write new entry newPosition = archive.Position(sizeof(EAREntry) + newEntry.nameLen + dataSize); archive.f.Seek(newPosition, start); archive.f.Write(&newEntry, sizeof(EAREntry), 1); archive.f.Write(newName, sizeof(char), newEntry.nameLen); // Fix the links if(entry.prev) { archive.f.Seek(entry.prev + OFFSET(EAREntry, next), start); archive.f.Write(&newPosition, sizeof(uint), 1); } if(entry.next) { archive.f.Seek(entry.next + OFFSET(EAREntry, prev), start); archive.f.Write(&newPosition, sizeof(uint), 1); } if(first == position) first = newPosition; if(last == position) last = newPosition; } else { // Change the name archive.f.Seek(position + OFFSET(EAREntry, nameLen), start); archive.f.Write(&newEntry.nameLen, sizeof(uint), 1); archive.f.Seek(position + sizeof(EAREntry), start); archive.f.Write(newName, sizeof(char), newEntry.nameLen); // There will be free space at the end of an entry with a shorter new name if(newEntry.nameLen < entry.nameLen) archive.AddFreeBlock(position + sizeof(EAREntry) + newEntry.nameLen + dataSize, entry.nameLen - newEntry.nameLen); } if(entry.nameLen != newEntry.nameLen) { byte * buffer; uint bufferSize = Min(dataSize, MAX_BUFFERSIZE); buffer = new byte[bufferSize]; if(buffer) { uint readPosition = position + sizeof(EAREntry) + entry.nameLen; uint writePosition = newPosition + sizeof(EAREntry) + newEntry.nameLen; uint c; for(c = 0; c c + bufferSize) ? bufferSize : (dataSize - c); archive.f.Seek(readPosition + c, start); archive.f.Read(buffer, size, 1); archive.f.Seek(writePosition + c, start); archive.f.Write(buffer, size, 1); } delete buffer; } if(newEntry.nameLen > entry.nameLen) { // Prevent the children to be deleted if(entry.type == ENTRY_FOLDER) { uint first = 0, last = 0; archive.f.Seek(position + sizeof(EAREntry) + entry.nameLen, start); archive.f.Write(&first, sizeof(uint), 1); archive.f.Write(&last, sizeof(uint), 1); } // Delete the old entry entry.prev = entry.next = 0; archive.f.Seek(position + sizeof(EAREntry) + entry.nameLen, start); archive.Delete(this, position, entry); } } result = true; } return result; } bool AddFromFile(const char * name, File input, FileStats stats, ArchiveAddMode addMode, int compression, int * ratio, uint * newPosition) { // Search for identical entry EAREntry oldEntry; uint oldPosition = archive.Find(this, name, oldEntry); return _AddFromFileAtPosition(oldEntry, oldPosition, name, input, stats, addMode, compression, ratio, newPosition); } bool AddFromFileAtPosition(uint oldPosition, const char * name, File input, FileStats stats, ArchiveAddMode addMode, int compression, int * ratio, uint * newPosition) { EAREntry oldEntry; if(oldPosition) { if(!archive.f.Seek(oldPosition, start) || !archive.f.Read(oldEntry, sizeof(EAREntry), 1)) return false; } return _AddFromFileAtPosition(oldEntry, oldPosition, name, input, stats, addMode, compression, ratio, newPosition); } bool _AddFromFileAtPosition(EAREntry oldEntry, uint oldPosition, const char * name, File input, FileStats stats, ArchiveAddMode addMode, int compression, int * ratio, uint * newPosition) { bool skip = false; FileStats oldStats { }; if(oldPosition) { oldStats.modified = (TimeStamp)oldEntry.modified; oldStats.created = (TimeStamp)oldEntry.created; } if(stats == null) { oldStats.size = input.GetSize(); stats = &oldStats; } switch(addMode) { // Add all files case replace: if(oldPosition) archive.Delete(this, oldPosition, oldEntry); break; // Only updates changed files case refresh: if(oldPosition && (oldEntry.size != stats.size || oldEntry.modified != (TimeStamp32)stats.modified || oldEntry.created != (TimeStamp32)stats.created)) archive.Delete(this, oldPosition, oldEntry); else skip = true; break; // Only updates changed or new files case update: if(oldPosition) { if(oldEntry.size != stats.size || oldEntry.modified != (TimeStamp32)stats.modified || oldEntry.created != (TimeStamp32)stats.created) archive.Delete(this, oldPosition, oldEntry); else skip = true; } break; } if(!skip) { EAREntry entry { }; uint position, size; byte * compressed = null; // Add the file entry.nameLen = strlen(name); entry.prev = last; entry.next = 0; entry.type = ENTRY_FILE; entry.size = stats.size; entry.created = (TimeStamp32)stats.created; entry.modified = (TimeStamp32)stats.modified; if(compression) { byte * uncompressed = new byte[entry.size]; if(uncompressed) { if(input.Read(uncompressed, 1, entry.size) == entry.size) { unsigned long destLen = entry.size + entry.size / 1000 + 12; compressed = new byte[destLen]; if(compressed) { compress2(compressed, &destLen, uncompressed, entry.size, compression); entry.cSize = (FileSize)destLen; } } delete uncompressed; } } if(!compressed) { entry.cSize = 0; if(ratio) *ratio = 0; } else if(ratio) *ratio = entry.size ? (entry.cSize * 1000 / entry.size) : 0; // Find block size = sizeof(EAREntry) + entry.nameLen + (entry.cSize ? entry.cSize : entry.size); position = archive.Position(size); // Write Header if(!archive.f.Seek(position, start) || !archive.f.Write(entry, sizeof(EAREntry), 1) || !archive.f.Write(name, entry.nameLen, 1)) { delete compressed; return false; } // Write File Data if(compressed) { if(!archive.f.Write(compressed, 1, entry.cSize)) { delete compressed; return false; } delete compressed; } else { byte buffer[8192]; uint c; int count = 1; for(c = 0; c= -pos) { position += pos; if(f) result = f.Seek(position + start, start); else result = true; } break; case end: if(pos < 0 && -pos <= (int)size) { position = size + pos; if(f) f.Seek(position + start, start); else result = true; } break; } return result; } uint Tell() { return position; } bool Eof() { return position >= size || (f && f.Eof()); } uint GetSize() { return size; } }; class EARFileSystem : FileSystem { File ::Open(const char * archive, const char * name, FileOpenMode mode) { File result = null; if(mode == read) { EARFile file {}; if(file) { char fileName[MAX_LOCATION]; EARHeader header; File f = EAROpenArchive(archive, &header); strcpy(fileName, name); #ifdef ECERE_STATIC if(!f && archive[0] == ':') { f = EAROpenArchive(":", &header); if(f) { strcpy(fileName, archive + 1); PathCat(fileName, name); } } #endif if(f) { EAREntry entry { }; if(EARGetEntry(f, entry, fileName, null).isFile) { if(entry.cSize) { byte * uncompressed = new byte[entry.size]; if(uncompressed) { byte * compressed = new byte[entry.cSize]; if(compressed) { if(f.Read(compressed, 1, entry.cSize) == entry.cSize) { unsigned long destLen = entry.size; uncompress(uncompressed, &destLen, compressed, entry.cSize); entry.size = (FileSize)destLen; } delete compressed; } file.position = 0; file.size = entry.size; file.buffer = uncompressed; result = file; } } else { file.start = f.Tell(); file.position = 0; file.size = entry.size; file.f = f; f = null; result = file; } } delete f; } if(!result) delete file; } } return result; } FileAttribs ::Exists(const char * archive, const char * fileName) { uint result = 0; EARHeader header; File f = EAROpenArchive(archive, &header); if(f) { EAREntry entry { }; result = EARGetEntry(f, entry, fileName, null); delete f; } return result; } bool ::GetSize(const char * archive, const char * fileName, FileSize * size) { bool result = false; EARHeader header; File f = EAROpenArchive(archive, &header); if(f) { EAREntry entry { }; if(EARGetEntry(f, entry, fileName, null)) *size = entry.size; delete f; result = true; } return result; } bool ::Stats(const char * archive, const char * fileName, FileStats stats) { bool result = false; EARHeader header; File f = EAROpenArchive(archive, &header); if(f) { EAREntry entry { }; if(EARGetEntry(f, entry, fileName, null)) { stats.size = entry.size; stats.accessed = 0; stats.modified = (TimeStamp)entry.modified; stats.created = (TimeStamp)entry.created; result = true; } delete f; } return result; } void ::FixCase(const char * archive, char * name) { #ifdef __WIN32__ EARHeader header; File f = EAROpenArchive(archive, &header); if(f) { EAREntry entry { }; char fileName[MAX_LOCATION] = ""; if(EARGetEntry(f, entry, name, fileName)) strcpy(name, fileName); delete f; } #endif } bool ::Find(FileDesc file, const char * archive, const char * name) { bool result = false; EARDir d {}; if(d) { EARHeader header; File f = EAROpenArchive(archive, &header); if(f) { EAREntry entry { }; if(EARGetEntry(f, entry, name, null).isDirectory) { uint first, last; sprintf(d.path, "<%s>%s", archive, name); d.f = f; f.Read(&first, sizeof(uint), 1); f.Read(&last, sizeof(uint), 1); d.next = first; if(d.next) { EAREntry entry { }; d.f.Seek(d.next, start); d.f.Read(entry, sizeof(EAREntry), 1); d.f.Read(file.name, 1, entry.nameLen); file.name[entry.nameLen] = '\0'; file.stats.attribs = { isDirectory = (entry.type == ENTRY_FOLDER), isFile = (entry.type != ENTRY_FOLDER) }; file.stats.accessed = file.stats.modified = (TimeStamp)entry.modified; file.stats.created = (TimeStamp)entry.created; file.stats.size = entry.size; strcpy(file.path, d.path); PathCat(file.path, file.name); d.next = entry.next; file.dir = (Dir)d; result = true; } } if(!result) delete f; } if(!result) delete d; } return result; } bool ::FindNext(FileDesc file) { bool result = false; EARDir d = (EARDir)file.dir; if(d.next) { EAREntry entry { }; d.f.Seek(d.next, start); d.f.Read(entry, sizeof(EAREntry), 1); d.f.Read(file.name, 1, entry.nameLen); file.name[entry.nameLen] = '\0'; file.stats.attribs = FileAttribs { isDirectory = (entry.type == ENTRY_FOLDER), isFile = (entry.type != ENTRY_FOLDER) }; file.stats.accessed = file.stats.modified = (TimeStamp)entry.modified; file.stats.created = (TimeStamp)entry.created; file.stats.size = entry.size; strcpy(file.path, d.path); PathCat(file.path, file.name); d.next = entry.next; result = true; } return result; } void ::CloseDir(FileDesc file) { EARDir d = (EARDir)file.dir; if(d.f) delete d.f; if(d) delete d; } #if !defined(ECERE_NOARCHIVE) && !defined(ECERE_VANILLA) Archive ::OpenArchive(const char * fileName, ArchiveOpenFlags flags) { Archive result = null; EARArchive archive { writeAccess = flags.writeAccess }; if(archive) { int try = flags.waitLock ? 10 : 0; for(; try >= 0; try--) { // Check for existing Archive if((archive.f = fileName ? (flags.buffered ? FileOpenBuffered : FileOpen)(fileName, flags.writeAccess ? readWrite : read) : TempFile { openMode = readWrite } )) { EARHeader header; bool opened = false; uint archiveSize = 0; archive.f.Seek(-(int)sizeof(uint), end); archive.f.Read(&archiveSize, sizeof(uint), 1); archive.f.Seek(-(int)archiveSize, end); archive.archiveStart = archive.f.Tell(); if(archive.f.Read(&header, sizeof(EARHeader), 1) == 1 && !memcmp(header.recognition, earRecognition, sizeof(earRecognition))) opened = true; if(!opened) { archive.f.Seek(0, start); archive.archiveStart = archive.f.Tell(); archiveSize = archive.f.GetSize(); if(archive.f.Read(&header, sizeof(EARHeader), 1) == 1 && !memcmp(header.recognition, earRecognition, sizeof(earRecognition))) opened = true; } if(opened) { // At this point we recognized the file as a valid eAR archive archive.rootDir = archive.archiveStart + sizeof(EARHeader); archive.totalSize = header.totalSize; archive.f.Seek(archive.rootDir, start); if(flags.buffered) { archive.freeBlocks.Add(FreeBlock { start = archive.rootDir + 2 * sizeof(uint), end = MAXDWORD }); archive.SubtractUsedBlocks(); } else { archive.freeBlocks.Add(FreeBlock { start = archive.archiveStart + (archiveSize - sizeof(uint)), end = MAXDWORD }); } /* if(!flags.writeAccess) { delete archive.f; archive.f = FileOpen(fileName, readWrite); } */ if(archive.f) { incref archive.f; result = archive; } } break; } else if(try > 0) Sleep(0.01); } // This piece of code will create a new archive as a new file or at the footer // of an existing file. if(!result && flags.writeAccess) { // If the file doesn't exist, create it if(!archive.f) { archive.f = FileOpen(fileName, writeRead); delete archive.f; archive.f = (flags.buffered ? FileOpenBuffered : FileOpen)(fileName, readWrite); } if(archive.f) { EARHeader header { EAR_RECOGNITION, MDWORD(0, 1) }; archive.f.Seek(0, end); archive.archiveStart = archive.f.Tell(); archive.freeBlocks.Add(FreeBlock { start = archive.archiveStart + sizeof(EARHeader), end = MAXDWORD }); // Write Header archive.f.Write(&header, sizeof(EARHeader), 1); { uint first = 0, last = 0; archive.f.Write(&first, sizeof(first), 1); archive.f.Write(&last, sizeof(last), 1); } archive.rootDir = 0; incref archive.f; result = archive; } } if(archive.f && flags.writeAccess && flags.exclusive && !archive.f.Lock(flags.exclusive ? exclusive : shared, 0, 0, flags.waitLock)) result = null; if(!result) { delete archive.f; delete archive; } else { // archive.f.handle = archive.f; } } return result; } #endif // !defined(ECERE_NOARCHIVE) && !defined(ECERE_VANILLA) bool ::QuerySize(const char * archive, FileSize * size) { bool result = false; EARHeader header; File f = EAROpenArchive(archive, &header); if(f) { *size = header.totalSize; result = true; delete f; } return result; } };