/* ----------------------------------------------------------------------------- GSFramework Copyright 2001-2013 Emmanuel Julien. All Rights Reserved. ----------------------------------------------------------------------------- */ #include #include "zlib.h" #include "archive/archive.h" #include "metafile/nml.h" #include "filesystem/filesystem.h" #include "thread/mutex.h" #include "platform.h" #include "log/log.h" using namespace GS; using namespace GS::NML; //------------------------------------------------------------------------------ #define __CorrectOffsetPadding \ {\ if (offset_padding)\ {\ size_t error = handle->Tell() % offset_padding;\ if (error)\ handle->Seek((long)(offset_padding - error));\ }\ } bool ArchiveIndex::Load(const char *uri) { index.Clear(); __LOG_V__ << "Loading archive index '" << uri << "'...\n"; File file; if (!Parser::Load(uri, file)) return false; // Grab index tag. Tag *index_tag = file.GetTag("Index;"); if (!index_tag) __ERR__(__LOG_E__ << "No archive index tag found in '" << uri << "'.\n", false) // Pool for entries. NMLTagForeach(t, *index_tag) if (t->name == "Entry") { Tag *id_tag = t->GetTypedTag("ID;", Variant::VariantString), *compressed_tag = t->GetTypedTag("CompLen", Variant::VariantInteger), *length_tag = t->GetTypedTag("Len", Variant::VariantInteger), *method_tag = t->GetTypedTag("Method", Variant::VariantString), *offset_tag = t->GetTypedTag("Offset", Variant::VariantInteger); if (id_tag && compressed_tag && length_tag && method_tag) { // Import the entry. if (ArchiveEntry *entry = new ArchiveEntry) { entry->path = id_tag->GetString(); entry->length = length_tag->GetInteger(); entry->compressed_length = compressed_tag->GetInteger(); entry->offset = offset_tag->GetInteger(); String method(method_tag->GetString()); if (method == "Raw") entry->method = ArchiveEntry::MethodRaw; else if (method == "Zlib") entry->method = ArchiveEntry::MethodZLibCompress; index.Add(entry); } else __LOG_E__ << "Failed to allocate new archive index entry.\n"; } else __LOG_E__ << "Incomplete index entry.\n"; } else __LOG_W__ << "Unexpected tag <" << t->name << "> in .\n"; __LOG_V__ << "Done, found " << index.GetCount() << " entries.\n"; return true; } bool ArchiveIndex::Save(const char *uri) { File file; Tag *index_tag = file.AddRoot("Index"); ListForeachPtr(ArchiveEntry *, entry, index) { // Create entry. Tag *entry_tag = index_tag->AddChild("Entry"); entry_tag->AddChild("ID", entry->path.c_str()); entry_tag->AddChild("CompLen", (int)entry->compressed_length); entry_tag->AddChild("Len", (int)entry->length); entry_tag->AddChild("Offset", (int)entry->offset); switch (entry->method) { case ArchiveEntry::MethodRaw: entry_tag->AddChild("Method", "Raw"); break; case ArchiveEntry::MethodZLibCompress: entry_tag->AddChild("Method", "Zlib"); break; } } return Parser::Save(uri, file); } ArchiveEntry *ArchiveIndex::FindEntry(const char *alias) const { ListForeachPtr(ArchiveEntry *, e, index) if (e->path == alias) return e; return NULL; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ bool Archive::LoadIndex(const char *uri) { return index.Load(uri); } bool Archive::SaveIndex(const char *uri) { return index.Save(uri); } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ bool Archive::OpenRead(const char *uri, const char *idx) { Close(); if (!uri) __ERR__(__LOG_E__ << "No archive to open.\n", false) if (!(handle = Platform::Get().io->Open(uri))) __ERR__(__LOG_E__ << "Failed to open archive '" << uri << "'.\n", false) // Check archive header. __CorrectOffsetPadding uint magic_word = handle->Read (); if (magic_word == 0x4E415244) // 'NARD' (padding support). { __CorrectOffsetPadding offset_padding = handle->Read (); __CorrectOffsetPadding size_padding = handle->Read (); revision = EnhancedLegacy; } else if (magic_word == 0x4E415243) // 'NARC' backward compatibility. { offset_padding = 0; size_padding = 0; revision = Legacy; } else { __LOG_E__ << "Invalid archive type '" << uri << "'.\n"; Close(); return false; } // Open or create index if none provided. if (!idx) { size_t alen = handle->GetSize(); if (verbose) __LOG__ << "No index provided, please wait while scanning archive...\n"; while (handle->Tell() < alen) { // Fetch alias. char tmp[512]; __CorrectOffsetPadding uint tsz = handle->Read (); if (tsz > 511) break; else { handle->Read((void *)tmp, tsz); tmp[tsz] = 0; } // Attributes. __CorrectOffsetPadding char cmp = handle->Read () & 1; __CorrectOffsetPadding uint len = handle->Read (), clen = len; if (cmp) { __CorrectOffsetPadding clen = handle->Read (); } // Store in index. if (ArchiveEntry *entry = new ArchiveEntry) { entry->path = tmp; entry->method = cmp; entry->length = len; entry->compressed_length = clen; entry->offset = handle->Tell(); index.index.Add(entry); if (verbose) { __LOG__ << "New entry: '" << entry->path << "'\n"; __LOG__ << "[method = " << entry->method << ", len = " << (uint)entry->length << ", clen = " << (uint)entry->compressed_length << ", offset = " << (uint)entry->offset << "].\n"; } } else __LOG_E__ << "Could not allocate new index entry.\n"; handle->Seek(clen); } if (verbose) __LOG__ << "Done, index table built.\n\n"; } else { LoadIndex(idx); if (verbose) ListForeachPtr(ArchiveEntry *, entry, index.index) { __LOG__ << "New entry: '" << entry->path << "'\n"; __LOG__ << "[method = " << entry->method << ", len = " << (uint)entry->length << ", clen = " << (uint)entry->compressed_length << ", offset = " << (uint)entry->offset << "].\n"; } } return true; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ bool Archive::CreateNew(const char *uri) { Close(); if (!(handle = Platform::Get().io->Open(uri, IO::ModeWrite))) __ERR__(__LOG_E__ << "Failed to open '" << uri << "' as output archive.\n", false) // Output header. __CorrectOffsetPadding handle->Write (0x4E415244); // 'NARD' __CorrectOffsetPadding handle->Write (offset_padding); __CorrectOffsetPadding handle->Write (size_padding); append_mode = true; return true; } void Archive::Close() { if (append_mode) { // EOF marker. handle->Write (0xffffffff); // Size padding. size_t size = handle->Tell(); size_t pad_count = size_padding ? size_padding - size % size_padding : 0; for (size_t n = 0; n < pad_count; ++n) handle->Write (0xff); // End of archive marker (entry id length > 512). } // Close archive. handle = NULL; index.index.Clear(); if (append_mode) __LOG__ << "Archive complete, closing.\n"; append_mode = false; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ bool Archive::FileRead(const char *path, void *out) { if (append_mode) __ERR__(__LOG_E__ << "Cannot load '" << path << "' (r/w mode error).\n", false) ArchiveEntry *entry = Exists(path); if (!entry) __ERR__(__LOG_E__ << "Could not find '" << path << "'.\n", false) Threading::MutexLock lock(access_mutex); size_t h_cursor = handle->Tell(); switch (entry->method) { case ArchiveEntry::MethodRaw: handle->Seek(entry->offset, IO::Base::SeekStart); // Note: Should be padded to the right offset already. if (handle->Read(out, entry->length) != entry->length) __LOG_E__ << "Failed to load raw source.\n"; break; case ArchiveEntry::MethodZLibCompress: { handle->Seek(entry->offset, IO::Base::SeekStart); // Note: Should be padded to the right offset already. Array cp((uint)(entry->compressed_length + 16)); if (!cp) __ERR__(__LOG_E__ << "Failed to allocated compressed memory support.\n", false) if (handle->Read((void *)cp, entry->compressed_length) != entry->compressed_length) __ERR__(__LOG_E__ << "Failed to load compressed source.\n", false) uLong out_len = (uLong)entry->length; uncompress((Bytef *)out, &out_len, (Bytef *)&cp[0], (uLong)entry->compressed_length); } break; } handle->Seek(h_cursor, IO::Base::SeekStart); return true; } //----------------------------------------------------------------- //------------------------------------------------------------------------------ ArchiveEntry *Archive::MemoryBlockWrite(const char *alias, const void *in, size_t len, int level) { if (!append_mode || !len) __ERR__(__LOG_E__ << "Cannot write '" << alias << "' (r/w mode error).\n", NULL) Threading::MutexLock lock(access_mutex); // Ensure alias uniqueness. if (ArchiveEntry *e = index.FindEntry(alias)) return e; // Sync index. ArchiveEntry *entry = new ArchiveEntry; if (!entry) __ERR__(__LOG_E__ << "Failed to allocate new index entry.\n", NULL) entry->path = alias; index.index.Add(entry); // Compress and add to archive. Array out; size_t clen; // Output alias. __CorrectOffsetPadding handle->Write (std::strlen(alias)); __CorrectOffsetPadding handle->Write((const void *)alias, std::strlen(alias)); if (verbose) __LOG_H__ << "Adding '" << alias << "' to archive...\n"; // Output data, ZLIB needs destination to be at least source * 100.1% + 12 bytes. bool add_raw = true; if (level >= 0) { clen = len + (len / 1000 + 1) + 12; if (out.Allocate((uint)clen) && (compress2((Bytef *)&out[0], (uLongf *)&clen, (const Bytef *)in, (uLong)len, level) == Z_OK)) { // @TODO compression method as a bit flag here. __CorrectOffsetPadding handle->Write (1); __CorrectOffsetPadding handle->Write (len); __CorrectOffsetPadding handle->Write (clen); // Write compressed block. __CorrectOffsetPadding entry->method = ArchiveEntry::MethodZLibCompress; entry->offset = handle->Tell(); entry->length = len; entry->compressed_length = clen; if (verbose) __LOG__ << "Was " << (uint)(len / 1000) << " KB, is now " << (uint)(clen / 1000) << " KB (" << (uint)(clen * 100 / len) <<"%).\n"; handle->Write((const void *)out, clen); add_raw = false; } else if (verbose) __LOG__ << "No compression achieved for '" << alias << "', adding uncompressed.\n"; } if (add_raw) { __CorrectOffsetPadding handle->Write (0); __CorrectOffsetPadding handle->Write (len); // Write raw block. __CorrectOffsetPadding entry->method = ArchiveEntry::MethodRaw; entry->offset = handle->Tell(); entry->length = len; entry->compressed_length = 0; handle->Write((const void *)in, len); } return entry; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ ArchiveEntry *Archive::FileWrite(const char *path, const char *alias, int level) { Array data; if (!Platform::Get().io->FileLoad(path, data)) return NULL; return MemoryBlockWrite(alias ? alias : path, &data[0], data.GetSize(), level); } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ Archive::Archive() { append_mode = false; offset_padding = 0; size_padding = 0; access_mutex = new Threading::Mutex; } Archive::~Archive() { Close(); } //------------------------------------------------------------------------------