Files
ReSkate/Engine/Vfs/builtin_pack_format.h
AyeZeeBB 43b998f285 Built-in content: Board Break and Road Rash, replacing their Thunderstore mods, packed with zstd
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).
2026-10-16 18:38:46 -05:00

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