Веб-сайт самохостера Lotigara

summaryrefslogtreecommitdiff
path: root/source/base/StarPackedAssetSource.cpp
blob: 897997893f26a276b9874e38a0dd400abcac058f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#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, String path, StreamOffset offset, StreamOffset size)
      : file(file), path(path), 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;
    }

    String deviceName() const override {
      return strf("{}:{}", file->deviceName(), path);
    }

    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);
    }

    IODevicePtr clone() override {
      auto cloned = make_shared<AssetReader>(file, path, fileOffset, assetSize);
      cloned->assetPos = assetPos;
      return cloned;
    }

    FilePtr file;
    String path;
    StreamOffset fileOffset;
    StreamOffset assetSize;
    StreamOffset assetPos;
  };

  auto p = m_index.ptr(path);
  if (!p)
    throw AssetSourceException::format("Requested file '{}' does not exist in the packed assets file", path);

  return make_shared<AssetReader>(m_packedFile, path, p->first, p->second);
}

ByteArray PackedAssetSource::read(String const& path) {
  auto p = m_index.ptr(path);
  if (!p)
    throw AssetSourceException::format("Requested file '{}' 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;
}

}