mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-17 16:17:06 +02:00
Fourth Studio's Board Break pack (ten decks and every sound) and Road Rash wounds are built into ReSkate.dll beside the FPV drone, unpacked under Mods/.reskate-builtin and always on. - The Thunderstore copies (Fourth_Studio-Board_Break_Pack, Fourth_Studio-Road_Rash and the deprecated Fourth_Studio-Board_Break, or the same put in by hand) are moved to the Recycle Bin as the launcher starts and dropped from mods.json; the merge leaves them out should one still be there, and the Thunderstore browser no longer lists, updates or installs them, a dependency on one included. - Board Break's deck list, sounds and deck settings look in the built-in packs too. - Names with spaces inside them (Board Break's "Body Impact 1.wav") unpack. - The build makes each zip in assets/builtin into one zstd stream (ReSkateBuiltinPacker, Engine/Vfs/builtin_pack_format.h), which the DLL carries: repeated files across a pack cost next to nothing. Board Break 15.0 -> 5.4 MB, the drone 6.0 -> 5.4 MB, Road Rash 36.1 -> 34.2 MB; ReSkate.dll 86 -> 70 MB. Plain zips still unpack. - Tests: the mods taken for built-in ones and their retirement, and a pack unpacking to exactly its files, refusing one that leaves its folder or is cut short (RESKATE_BUILTIN_PACKS also unpacks the packs a build made).
42 lines
1.9 KiB
C++
42 lines
1.9 KiB
C++
#pragma once
|
|
// The built-in pack format (Engine/Vfs/builtin_packs.h), as BuiltinPacker writes it at build time
|
|
// from a zip in assets/builtin and ReSkate unpacks it: one zstd frame (with its content size and a
|
|
// checksum) holding
|
|
// "RSKPACK1", u32 file count,
|
|
// for each file: u16 name length, the name (UTF-8, '/' between folders), u64 size,
|
|
// then every file's bytes in the same order.
|
|
// Numbers little-endian.
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cstdint>
|
|
#include <string_view>
|
|
|
|
namespace dingosdk::mods::builtin_pack {
|
|
inline constexpr std::array<char, 8> magic{'R', 'S', 'K', 'P', 'A', 'C', 'K', '1'};
|
|
// The most a pack may unpack to, and the largest zstd window it may use (zstd's own default
|
|
// limit for decoding, 128 MiB).
|
|
inline constexpr std::uint64_t max_bytes = 512ull * 1024 * 1024;
|
|
inline constexpr int max_window_log = 27;
|
|
|
|
// A path inside a pack: relative, forward slashes, no "..", plain characters (and spaces inside a
|
|
// name, as in a sound's "Body Impact 1.wav", but never at either end of one).
|
|
inline bool safe_name(std::string_view name) {
|
|
if (name.empty() || name.size() > 260 || name.front() == '/' || name.find(':') != std::string_view::npos ||
|
|
name.find('\\') != std::string_view::npos)
|
|
return false;
|
|
std::size_t start = 0;
|
|
while (start <= name.size()) {
|
|
const auto end = std::min(name.find('/', start), name.size());
|
|
const auto part = name.substr(start, end - start);
|
|
if (part.empty() || part == "." || part == ".." || part.front() == ' ' || part.back() == ' ') return false;
|
|
if (!std::all_of(part.begin(), part.end(), [](char c) {
|
|
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '.' || c == '_' ||
|
|
c == '-' || c == ' ';
|
|
}))
|
|
return false;
|
|
start = end + 1;
|
|
}
|
|
return true;
|
|
}
|
|
}
|