mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-24 09:26:13 +02:00
MY MODS: a table with an animated drag for the load order, a conflicts bar with Check conflicts, and a Resolve popup whose choices are kept. The check runs at startup and after changes; conflicts no longer show as problems, block Play, or hold mod files during an update. Conflicts: only game EBX that cannot be combined, or Lua scripts with different versions; colourways and same-name additions are not conflicts. Official mods: installed in Mods/<Namespace-Name> and turned on or off through mods.json, so they show in MY MODS; the old .reskate-official folder is moved over. Merge: a scan cache (Mods/.reskate-scan.txt) keeps each unchanged mod's repair, mesh and texture scans, so big maps are not re-read every merge. Launcher: an announcement from the backend shows under STATUS, with HIDE.
87 lines
4.2 KiB
C++
87 lines
4.2 KiB
C++
#pragma once
|
|
// Read access to a data layer's cas archives: <root>/Win32/<package>/cas_NN.cas,
|
|
// with each install chunk's package directory taken from layout.toc.
|
|
#include "native_db.h"
|
|
#include "Engine/Resource/binary_bundle.h"
|
|
#include <cstdint>
|
|
#include <filesystem>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <set>
|
|
#include <span>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace dingosdk::vfs {
|
|
// "cas_NN.cas"
|
|
std::string archive_file(std::uint16_t archive);
|
|
// Each record of layout.toc's install chunk file tables: a little-endian
|
|
// archive index followed by the install chunk id it belongs to.
|
|
template<class Visit>
|
|
void for_each_install_chunk_file(std::span<const unsigned char> table, Visit&& visit) {
|
|
for (std::size_t at = 0; at + 8 <= table.size(); at += 8) {
|
|
const auto archive = static_cast<std::uint16_t>(table[at] | (table[at + 1] << 8));
|
|
std::uint32_t id{};
|
|
for (int byte = 3; byte >= 0; --byte)
|
|
id = (id << 8) | static_cast<std::uint32_t>(table[at + 2 + byte]);
|
|
visit(id, archive);
|
|
}
|
|
}
|
|
// A parsed layout.toc. The tree's byte arrays are views into `buffer`, so the
|
|
// two travel together (moving a vector keeps its storage).
|
|
struct Layout {
|
|
std::vector<unsigned char> buffer;
|
|
native_db::Node root;
|
|
};
|
|
// Parses a layout.toc file, envelope included.
|
|
Layout read_layout(const std::filesystem::path& path);
|
|
|
|
class GameArchives {
|
|
public:
|
|
// `root` is a data layer's directory (the game's Data folder); its layout
|
|
// names the package directory of every install chunk.
|
|
GameArchives(std::filesystem::path root, const native_db::Node& layout);
|
|
|
|
[[nodiscard]] std::vector<std::byte> read(const std::filesystem::path& root, const frostbite::CasIdentifier& location,
|
|
std::uint32_t offset, std::uint32_t size) const;
|
|
// The bundle manifest is stored uncompressed, unlike the asset payloads
|
|
// around it, so raw comes first and the block decoder is the fallback.
|
|
[[nodiscard]] frostbite::BinaryBundle read_manifest(const std::filesystem::path& root,
|
|
const frostbite::CasIdentifier& location, std::uint32_t offset, std::uint32_t size,
|
|
const std::filesystem::path& gameRoot) const;
|
|
|
|
// "<package>/cas_NN.cas" for a log line; never throws for an unknown chunk.
|
|
[[nodiscard]] std::string describe(const frostbite::CasIdentifier& location) const;
|
|
// The archive's size on disk under `root`, remembered per file; nullopt
|
|
// when the file is not there or its package directory is unknown.
|
|
[[nodiscard]] std::optional<std::uint64_t> archive_size(const std::filesystem::path& root,
|
|
const frostbite::CasIdentifier& location);
|
|
[[nodiscard]] const std::string* find_directory(std::uint32_t installChunk) const;
|
|
[[nodiscard]] const std::string& directory(std::uint32_t installChunk) const;
|
|
// Every install chunk whose archives live in `directory`.
|
|
[[nodiscard]] std::vector<std::uint32_t> chunks_in(const std::string& directory) const;
|
|
[[nodiscard]] const std::filesystem::path& root() const noexcept { return root_; }
|
|
|
|
static std::optional<std::uint16_t> archive_index(const std::string& stem);
|
|
// From here on every archive read stays open until this object goes: for a merge, which
|
|
// reads tens of thousands of payloads out of a few hundred files. Opening a file costs
|
|
// far more than reading from it (the PC's anti-virus checks every open and close).
|
|
// Reads may then come from any thread.
|
|
void keep_open();
|
|
// Closes an archive kept open, before the file is replaced by another.
|
|
void forget(const std::filesystem::path& file) const;
|
|
// Closes every archive kept open; a later read opens its file again. For one kept between
|
|
// jobs, so the files are not held while nothing reads them (a mod could not be updated).
|
|
void close_all() const;
|
|
|
|
private:
|
|
struct Open; // the archives kept open (keep_open); null: each read opens its file
|
|
std::shared_ptr<Open> open_;
|
|
std::filesystem::path root_;
|
|
std::map<std::string, std::set<std::uint16_t>> directories_;
|
|
std::map<std::uint32_t, std::string> chunkDirectory_;
|
|
std::map<std::wstring, std::optional<std::uint64_t>> sizes_;
|
|
};
|
|
}
|