| 236 | } |
| 237 | |
| 238 | void TitleCatalog::importTitleListCache(std::vector<Title>& saves, std::vector<Title>& extdatas, IconStore& icons) |
| 239 | { |
| 240 | FSStream inputsaves(Archive::sdmc(), saveCachePath, FS_OPEN_READ); |
| 241 | u32 bytesSaves = inputsaves.size(); |
| 242 | std::unique_ptr<u8[]> cachesaves(new u8[bytesSaves]); |
| 243 | u32 readSaves = inputsaves.read(cachesaves.get(), bytesSaves); |
| 244 | inputsaves.close(); |
| 245 | // A cache whose size isn't a whole number of entries, or that read short, is |
| 246 | // corrupt: treat it as empty rather than decoding desynced/garbage entries. |
| 247 | u32 sizesaves = (bytesSaves % TitleCache::ENTRY_SIZE == 0 && readSaves == bytesSaves) ? bytesSaves / TitleCache::ENTRY_SIZE : 0; |
| 248 | |
| 249 | FSStream inputextdatas(Archive::sdmc(), extdataCachePath, FS_OPEN_READ); |
| 250 | u32 bytesExtdatas = inputextdatas.size(); |
| 251 | std::unique_ptr<u8[]> cacheextdatas(new u8[bytesExtdatas]); |
| 252 | u32 readExtdatas = inputextdatas.read(cacheextdatas.get(), bytesExtdatas); |
| 253 | inputextdatas.close(); |
| 254 | u32 sizeextdatas = (bytesExtdatas % TitleCache::ENTRY_SIZE == 0 && readExtdatas == bytesExtdatas) ? bytesExtdatas / TitleCache::ENTRY_SIZE : 0; |
| 255 | |
| 256 | mLimit = sizesaves + sizeextdatas; |
| 257 | |
| 258 | saves.reserve(sizesaves); |
| 259 | extdatas.reserve(sizeextdatas); |
| 260 | |
| 261 | // fill the lists with blank titles first |
| 262 | for (size_t i = 0, sz = std::max(sizesaves, sizeextdatas); i < sz; i++) { |
| 263 | Title title; |
| 264 | title.load(); |
| 265 | if (i < sizesaves) { |
| 266 | saves.push_back(title); |
| 267 | } |
| 268 | if (i < sizeextdatas) { |
| 269 | extdatas.push_back(title); |
| 270 | } |
| 271 | } |
| 272 | |
| 273 | // Map each already-loaded save id to its first and last index in `saves`, so |
| 274 | // the extdata pass can reuse a decoded save (they share entries) with an O(1) |
| 275 | // lookup instead of a linear scan per extdata — this runs on the startup fast |
| 276 | // path. Duplicate ids only occur for the cartridge title, hence tracking both |
| 277 | // ends (see the i != 0 && first == 0 case below). |
| 278 | std::unordered_map<u64, size_t> firstIdx; |
| 279 | std::unordered_map<u64, size_t> lastIdx; |
| 280 | firstIdx.reserve(sizesaves); |
| 281 | lastIdx.reserve(sizesaves); |
| 282 | |
| 283 | for (size_t i = 0; i < sizesaves; i++) { |
| 284 | const u8* titleData = cachesaves.get() + i * TitleCache::ENTRY_SIZE; |
| 285 | saves.at(i) = TitleCache::decode(titleData, icons); |
| 286 | u64 id = TitleCache::readId(titleData); |
| 287 | firstIdx.emplace(id, i); // keeps the first occurrence |
| 288 | lastIdx[id] = i; // always the latest occurrence |
| 289 | |
| 290 | mCounter++; |
| 291 | } |
| 292 | |
| 293 | for (size_t i = 0; i < sizeextdatas; i++) { |
| 294 | const u8* titleData = cacheextdatas.get() + i * TitleCache::ENTRY_SIZE; |
| 295 | |