Files
ReSkate/Engine/Resource/shader_lookup.cpp
T
AyeZeeBB 9f93c3cbb2 Shader tables: mods' are merged as in 2.0.7 again, and the game's song scorer stays within its list
Shader tables
- Reverts a61f82c ("Shader crash fix"). Since 2.0.8 a mod's own shader
  programs were copied into the merged table, and the tables that cosmetic
  bundles carry were merged across mods. Each such bundle then held other
  mods' programs, and the game built them as the bundle loaded, before the
  files with their compiled variants were there. That was 2.0.8's most
  reported crash (Skate.exe+0x3f86da6, some forty machines in ten minutes).
  2.0.9's guard passed such a program over, and the same players then
  crashed a little later in cosmetic loading (+0x3f7bb65, +0x16fe1a6,
  +0x3dc07ed).
- Whatever crash a61f82c fixed comes back with this for the mods that
  needed it.
- The guard stays.

Music
- The game's read of one song for its scorer (0x14f23a0) is replaced by the
  same read with the check it lacks: a place past the end of the playlist
  asset's songs has no song, which the game already answers with 0. 2.0.9's
  check in music_select_next_hook looked at what the queue held before the
  game was asked and did not see these; the game asked for places such as
  0xd570 and crashed reading them (7 reports on 6 machines).
- Said in the log the first eight times.

Not confirmed in game: neither change has been run by a player who was
crashing.
2026-10-13 18:44:15 -05:00

133 lines
5.1 KiB
C++

#include "shader_lookup.h"
#include <cstring>
#include <map>
#include <stdexcept>
#include <string>
namespace dingosdk::frostbite::shader {
namespace {
template <typename Type>
Type read(const std::span<const std::byte> bytes, const std::size_t offset) {
if (offset > bytes.size() || sizeof(Type) > bytes.size() - offset)
throw std::out_of_range("Shader lookup read exceeds its payload");
Type result{};
std::memcpy(&result, bytes.data() + offset, sizeof(result));
return result;
}
template <typename Type>
void append(std::vector<std::byte>& bytes, const Type value) {
const auto offset = bytes.size();
bytes.resize(offset + sizeof(value));
std::memcpy(bytes.data() + offset, &value, sizeof(value));
}
using ProgramRows = std::map<std::uint64_t, std::vector<std::uint32_t>>;
using TextureRows = std::map<std::uint64_t, std::uint64_t>;
// Where the lookup section starts, after the compiled programs and relocations.
std::size_t lookup_offset(const Table& table) {
if (table.resourceMeta.size() != 16)
throw std::runtime_error("Program lookup metadata is not 16 bytes");
const auto programSize = read<std::uint32_t>(table.resourceMeta, 0);
const auto relocationSize = read<std::uint32_t>(table.resourceMeta, 4);
const auto lookupSize = read<std::uint32_t>(table.resourceMeta, 8);
const auto offset = static_cast<std::uint64_t>(programSize) + relocationSize;
if (offset > table.resource.size() || lookupSize < 4 ||
lookupSize != table.resource.size() - offset)
throw std::runtime_error("Program lookup metadata does not match its payload");
return static_cast<std::size_t>(offset);
}
ProgramRows read_program_rows(const Table& table) {
auto offset = lookup_offset(table);
const auto count = read<std::uint32_t>(table.resource, offset);
offset += 4;
ProgramRows rows;
for (std::uint32_t index = 0; index < count; ++index) {
const auto key = read<std::uint64_t>(table.resource, offset);
const auto values = read<std::uint32_t>(table.resource, offset + 8);
offset += 12;
if (!values || values > 4096 ||
static_cast<std::uint64_t>(values) * 4 > table.resource.size() - offset)
throw std::runtime_error("Program lookup rows are invalid");
auto& row = rows[key];
row.reserve(values);
for (std::uint32_t value = 0; value < values; ++value) {
row.push_back(read<std::uint32_t>(table.resource, offset));
offset += 4;
}
}
if (offset != table.resource.size())
throw std::runtime_error("Program lookup has trailing data");
return rows;
}
TextureRows read_texture_rows(const Table& table) {
if (table.resource.size() % 16)
throw std::runtime_error("Texture lookup does not have its native layout");
TextureRows rows;
for (std::size_t offset = 0; offset < table.resource.size(); offset += 16)
rows.emplace(read<std::uint64_t>(table.resource, offset),
read<std::uint64_t>(table.resource, offset + 8));
return rows;
}
} // namespace
MergedTable merge_program_lookup(const Table& base, const std::span<const Table> edits) {
const auto start = lookup_offset(base);
auto rows = read_program_rows(base);
const auto baseRows = rows;
MergedTable merged;
for (const auto& edit : edits) {
for (const auto& [key, values] : read_program_rows(edit)) {
if (baseRows.contains(key)) continue;
const auto [row, inserted] = rows.try_emplace(key, values);
if (inserted) ++merged.added;
else if (row->second != values) ++merged.conflicts;
}
}
merged.resource.assign(base.resource.begin(),
base.resource.begin() + static_cast<std::ptrdiff_t>(start));
append(merged.resource, static_cast<std::uint32_t>(rows.size()));
for (const auto& [key, values] : rows) {
append(merged.resource, key);
append(merged.resource, static_cast<std::uint32_t>(values.size()));
for (const auto value : values) append(merged.resource, value);
}
merged.resourceMeta.assign(base.resourceMeta.begin(), base.resourceMeta.end());
const auto size = static_cast<std::uint32_t>(merged.resource.size() - start);
std::memcpy(merged.resourceMeta.data() + 8, &size, sizeof(size));
return merged;
}
MergedTable merge_texture_lookup(const Table& base, const std::span<const Table> edits) {
auto rows = read_texture_rows(base);
const auto baseRows = rows;
MergedTable merged;
for (const auto& edit : edits) {
for (const auto& [key, value] : read_texture_rows(edit)) {
if (baseRows.contains(key)) continue;
const auto [row, inserted] = rows.try_emplace(key, value);
if (inserted) ++merged.added;
else if (row->second != value) ++merged.conflicts;
}
}
merged.resource.reserve(rows.size() * 16);
for (const auto& [key, value] : rows) {
append(merged.resource, key);
append(merged.resource, value);
}
merged.resourceMeta.assign(base.resourceMeta.begin(), base.resourceMeta.end());
return merged;
}
} // namespace dingosdk::frostbite::shader