osb/source/base/StarPackedAssetSource.cpp
2023-06-20 14:33:09 +10:00

159 lines
4.7 KiB
C++

#include "StarPackedAssetSource.hpp"
#include "StarDirectoryAssetSource.hpp"
#include "StarOrderedSet.hpp"
#include "StarDataStreamDevices.hpp"
#include "StarDataStreamExtra.hpp"
#include "StarSha256.hpp"
#include "StarFile.hpp"
namespace Star {
void PackedAssetSource::build(DirectoryAssetSource& directorySource, String const& targetPackedFile,
StringList const& extensionSorting, BuildProgressCallback progressCallback) {
FilePtr file = File::open(targetPackedFile, IOMode::ReadWrite | IOMode::Truncate);
DataStreamIODevice ds(file);
ds.writeData("SBAsset6", 8);
// Skip 8 bytes, this will be a pointer to the index once we are done.
ds.seek(8, IOSeek::Relative);
// Insert every found entry into the packed file, and also simultaneously
// compute the full index.
StringMap<pair<uint64_t, uint64_t>> index;
OrderedHashSet<String> extensionOrdering;
for (auto const& str : extensionSorting)
extensionOrdering.add(str.toLower());
StringList assetPaths = directorySource.assetPaths();
// Returns a value for the asset that can be used to predictably sort assets
// by name and then by extension, where every extension listed in
// "extensionSorting" will come first, and then any extension not listed will
// come after.
auto getOrderingValue = [&extensionOrdering](String const& asset) -> pair<size_t, String> {
String extension;
auto lastDot = asset.findLast(".");
if (lastDot != NPos)
extension = asset.substr(lastDot + 1);
if (auto i = extensionOrdering.indexOf(extension.toLower())) {
return {*i, asset.toLower()};
} else {
return {extensionOrdering.size(), asset.toLower()};
}
};
assetPaths.sort([&getOrderingValue](String const& a, String const& b) {
return getOrderingValue(a) < getOrderingValue(b);
});
for (size_t i = 0; i < assetPaths.size(); ++i) {
String const& assetPath = assetPaths[i];
ByteArray contents = directorySource.read(assetPath);
if (progressCallback)
progressCallback(i, assetPaths.size(), directorySource.toFilesystem(assetPath), assetPath);
index.add(assetPath, {ds.pos(), contents.size()});
ds.writeBytes(contents);
}
uint64_t indexStart = ds.pos();
ds.writeData("INDEX", 5);
ds.write(directorySource.metadata());
ds.write(index);
ds.seek(8);
ds.write(indexStart);
}
PackedAssetSource::PackedAssetSource(String const& filename) {
m_packedFile = File::open(filename, IOMode::Read);
DataStreamIODevice ds(m_packedFile);
if (ds.readBytes(8) != ByteArray("SBAsset6", 8))
throw AssetSourceException("Packed assets file format unrecognized!");
uint64_t indexStart = ds.read<uint64_t>();
ds.seek(indexStart);
ByteArray header = ds.readBytes(5);
if (header != ByteArray("INDEX", 5))
throw AssetSourceException("No index header found!");
ds.read(m_metadata);
ds.read(m_index);
}
JsonObject PackedAssetSource::metadata() const {
return m_metadata;
}
StringList PackedAssetSource::assetPaths() const {
return m_index.keys();
}
IODevicePtr PackedAssetSource::open(String const& path) {
struct AssetReader : public IODevice {
AssetReader(FilePtr file, StreamOffset offset, StreamOffset size)
: file(file), fileOffset(offset), assetSize(size), assetPos(0) {
setMode(IOMode::Read);
}
size_t read(char* data, size_t len) override {
len = min<StreamOffset>(len, assetSize - assetPos);
file->readFullAbsolute(fileOffset + assetPos, data, len);
assetPos += len;
return len;
}
size_t write(char const*, size_t) override {
throw IOException("Assets IODevices are read-only");
}
StreamOffset size() override {
return assetSize;
}
StreamOffset pos() override {
return assetPos;
}
bool atEnd() override {
return assetPos >= assetSize;
}
void seek(StreamOffset p, IOSeek mode) override {
if (mode == IOSeek::Absolute)
assetPos = p;
else if (mode == IOSeek::Relative)
assetPos = clamp<StreamOffset>(assetPos + p, 0, assetSize);
else
assetPos = clamp<StreamOffset>(assetSize - p, 0, assetSize);
}
FilePtr file;
StreamOffset fileOffset;
StreamOffset assetSize;
StreamOffset assetPos;
};
auto p = m_index.ptr(path);
if (!p)
throw AssetSourceException::format("Requested file '%s' does not exist in the packed assets file", path);
return make_shared<AssetReader>(m_packedFile, p->first, p->second);
}
ByteArray PackedAssetSource::read(String const& path) {
auto p = m_index.ptr(path);
if (!p)
throw AssetSourceException::format("Requested file '%s' does not exist in the packed assets file", path);
ByteArray data(p->second, 0);
m_packedFile->readFullAbsolute(p->first, data.ptr(), p->second);
return data;
}
}