441 lines
12 KiB
C++
441 lines
12 KiB
C++
/* -----------------------------------------------------------------------------
|
|
GSFramework
|
|
Copyright 2001-2013 Emmanuel Julien. All Rights Reserved.
|
|
----------------------------------------------------------------------------- */
|
|
|
|
|
|
#include <cstring>
|
|
#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 <Index>.\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 <uint> ();
|
|
|
|
if (magic_word == 0x4E415244) // 'NARD' (padding support).
|
|
{
|
|
__CorrectOffsetPadding
|
|
offset_padding = handle->Read <int> ();
|
|
__CorrectOffsetPadding
|
|
size_padding = handle->Read <int> ();
|
|
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 <uint> ();
|
|
|
|
if (tsz > 511)
|
|
break;
|
|
|
|
else
|
|
{
|
|
handle->Read((void *)tmp, tsz);
|
|
tmp[tsz] = 0;
|
|
}
|
|
|
|
// Attributes.
|
|
__CorrectOffsetPadding
|
|
char cmp = handle->Read <uchar> () & 1;
|
|
__CorrectOffsetPadding
|
|
uint len = handle->Read <uint> (), clen = len;
|
|
if (cmp)
|
|
{
|
|
__CorrectOffsetPadding
|
|
clen = handle->Read <uint> ();
|
|
}
|
|
|
|
// 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 <uint> (0x4E415244); // 'NARD'
|
|
__CorrectOffsetPadding
|
|
handle->Write <int> (offset_padding);
|
|
__CorrectOffsetPadding
|
|
handle->Write <int> (size_padding);
|
|
|
|
append_mode = true;
|
|
return true;
|
|
}
|
|
void Archive::Close()
|
|
{
|
|
if (append_mode)
|
|
{
|
|
// EOF marker.
|
|
handle->Write <uint> (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 <uchar> (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 <char> 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 <char> out;
|
|
size_t clen;
|
|
|
|
// Output alias.
|
|
__CorrectOffsetPadding
|
|
handle->Write <uint> (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 <uchar> (1);
|
|
__CorrectOffsetPadding
|
|
handle->Write <uint> (len);
|
|
__CorrectOffsetPadding
|
|
handle->Write <uint> (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 <uchar> (0);
|
|
__CorrectOffsetPadding
|
|
handle->Write <uint> (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 <char> 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();
|
|
}
|
|
//------------------------------------------------------------------------------
|