// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved // Please see LICENSE.md in repository root for license information // https://github.com/AtomicGameEngine/AtomicGameEngine // // Portions Copyright (c) 2008-2014 the Urho3D project. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // #include "AtomicEditor.h" #include "Atomic/Core/StringUtils.h" #include #include #include #include #include "BuildBase.h" #include "ResourcePackager.h" namespace ToolCore { ResourcePackager::ResourcePackager(Context* context, BuildBase* buildBase) : Object(context) , buildBase_(buildBase) , checksum_(0) { } ResourcePackager::~ResourcePackager() { } bool ResourcePackager::WritePackageFile(const String& destFilePath) { buildBase_->BuildLog("Writing package"); SharedPtr dest(new File(context_, destFilePath, FILE_WRITE)); if (!dest->IsOpen()) { buildBase_->BuildError("Could not open output file " + destFilePath); return false; } // Write ID, number of files & placeholder for checksum WriteHeader(dest); for (unsigned i = 0; i < resourceEntries_.Size(); i++) { BuildResourceEntry* entry = resourceEntries_[i]; // Write entry (correct offset is still unknown, will be filled in later) dest->WriteString(entry->packagePath_); dest->WriteUInt(entry->offset_); dest->WriteUInt(entry->size_); dest->WriteUInt(entry->checksum_); } unsigned totalDataSize = 0; // Write file data, calculate checksums & correct offsets for (unsigned i = 0; i < resourceEntries_.Size(); i++) { BuildResourceEntry* entry = resourceEntries_[i]; entry->offset_ = dest->GetSize(); File srcFile(context_, entry->absolutePath_); if (!srcFile.IsOpen()) { buildBase_->BuildError("Could not open input file " + entry->absolutePath_); return false; } unsigned dataSize = entry->size_; totalDataSize += dataSize; SharedArrayPtr buffer(new unsigned char[dataSize]); if (srcFile.Read(&buffer[0], dataSize) != dataSize) { buildBase_->BuildError("Could not read input file " + entry->absolutePath_); return false; } srcFile.Close(); for (unsigned j = 0; j < dataSize; ++j) { checksum_ = SDBMHash(checksum_, buffer[j]); entry->checksum_ = SDBMHash(entry->checksum_, buffer[j]); } // might not want to compress for WebGL //if (!compress_) //{ // PrintLine(entries_[i].name_ + " size " + String(dataSize)); // dest.Write(&buffer[0], entries_[i].size_); //} //else //{ unsigned compressedBlockSize_ = 32768; SharedArrayPtr compressBuffer(new unsigned char[LZ4_compressBound(compressedBlockSize_)]); unsigned pos = 0; unsigned totalPackedBytes = 0; while (pos < dataSize) { unsigned unpackedSize = compressedBlockSize_; if (pos + unpackedSize > dataSize) unpackedSize = dataSize - pos; unsigned packedSize = LZ4_compressHC((const char*)&buffer[pos], (char*)compressBuffer.Get(), unpackedSize); if (!packedSize) { buildBase_->BuildError("LZ4 compression failed for file " + entry->absolutePath_ + " at offset " + pos); return false; } dest->WriteUShort(unpackedSize); dest->WriteUShort(packedSize); dest->Write(compressBuffer.Get(), packedSize); totalPackedBytes += 6 + packedSize; pos += unpackedSize; } buildBase_->BuildLog(entry->absolutePath_ + " in " + String(dataSize) + " out " + String(totalPackedBytes)); } //} // Write package size to the end of file to allow finding it linked to an executable file unsigned currentSize = dest->GetSize(); dest->WriteUInt(currentSize + sizeof(unsigned)); // Write header again with correct offsets & checksums dest->Seek(0); WriteHeader(dest); for (unsigned i = 0; i < resourceEntries_.Size(); i++) { BuildResourceEntry* entry = resourceEntries_[i]; dest->WriteString(entry->packagePath_); dest->WriteUInt(entry->offset_); dest->WriteUInt(entry->size_); dest->WriteUInt(entry->checksum_); } buildBase_->BuildLog("Number of files " + String(resourceEntries_.Size())); buildBase_->BuildLog("File data size " + String(totalDataSize)); buildBase_->BuildLog("Package size " + String(dest->GetSize())); return true; } void ResourcePackager::WriteHeader(File* dest) { dest->WriteFileID("ULZ4"); dest->WriteUInt(resourceEntries_.Size()); dest->WriteUInt(checksum_); } void ResourcePackager::GeneratePackage(const String& destFilePath) { for (unsigned i = 0; i < resourceEntries_.Size(); i++) { BuildResourceEntry* entry = resourceEntries_[i]; File file(context_); if (!file.Open(entry->absolutePath_)) { buildBase_->BuildError(Atomic::ToString("Could not open resource file %s", entry->absolutePath_.CString())); return; } if (!file.GetSize()) { return; } entry->size_ = file.GetSize(); } WritePackageFile(destFilePath); } void ResourcePackager::AddResourceEntry(BuildResourceEntry* entry) { resourceEntries_.Push(entry); } }