// The shader program lookup's merge: the rows mods add are combined onto the base, and a row // that points past the base's shader records is left out (the game reads such an index outside // its shader database). #include "Engine/Resource/shader_lookup.h" #ifdef _WIN32 #include #endif #include #include #include #include #include namespace fb = dingosdk::frostbite; namespace { int failures = 0; void check(bool condition, const char* what) { if (!condition) { std::cerr << "FAILED: " << what << "\n"; ++failures; } } template void append(std::vector& bytes, Type value) { const auto at = bytes.size(); bytes.resize(at + sizeof(value)); std::memcpy(bytes.data() + at, &value, sizeof(value)); } template Type read(const std::vector& bytes, std::size_t at) { Type value{}; std::memcpy(&value, bytes.data() + at, sizeof(value)); return value; } // A program lookup: a blob of `records` records (only its count matters here), no relocations, // and the rows (key -> record indices). struct Lookup { std::vector resource, meta; fb::shader::Table table() const { return {resource, meta}; } }; Lookup lookup(std::uint32_t records, const std::map>& rows) { Lookup out; append(out.resource, records); append(out.resource, std::uint32_t{0}); // the rest of the blob const auto program = static_cast(out.resource.size()); append(out.resource, static_cast(rows.size())); for (const auto& [key, values] : rows) { append(out.resource, key); append(out.resource, static_cast(values.size())); for (const auto value : values) append(out.resource, value); } append(out.meta, program); append(out.meta, std::uint32_t{0}); append(out.meta, static_cast(out.resource.size() - program)); append(out.meta, std::uint32_t{0}); return out; } // A complete preserved stock heap with an appended program array. This is // intentionally different from merely sharing record counts or numeric indices. Lookup preserved_lookup(bool extended, const std::map>& rows) { Lookup out; const std::uint32_t stockSize = 208; const std::uint64_t start = extended ? 208 : 16; const std::uint32_t count = extended ? 2 : 1; const std::uint32_t size = extended ? 544 : stockSize; out.resource.resize(size); const std::uint64_t heap = 192; std::memcpy(out.resource.data(), &count, 4); std::memcpy(out.resource.data() + 4, &start, 8); // The first array's count/pointer, within the stock record at 16. const std::uint32_t one = 1; std::memcpy(out.resource.data() + 28, &one, 4); std::memcpy(out.resource.data() + 32, &heap, 8); out.resource[192] = std::byte{7}; if (extended) std::memcpy(out.resource.data() + 208, out.resource.data() + 16, 168); append(out.resource, std::uint32_t{4}); append(out.resource, std::uint32_t{32}); if (extended) append(out.resource, std::uint32_t{224}); append(out.resource, static_cast(rows.size())); for (const auto& [key, values] : rows) { append(out.resource, key); append(out.resource, static_cast(values.size())); for (const auto v : values) append(out.resource, v); } const std::uint32_t fix = extended ? 12 : 8; append(out.meta, size); append(out.meta, fix); append(out.meta, static_cast(out.resource.size() - size - fix)); append(out.meta, std::uint32_t{}); return out; } // The merged rows, read back. std::map> rows_of(const fb::shader::MergedTable& merged) { std::size_t at = static_cast(read(merged.resourceMeta, 0)) + read(merged.resourceMeta, 4); const auto count = read(merged.resource, at); at += 4; std::map> rows; for (std::uint32_t row = 0; row < count; ++row) { const auto key = read(merged.resource, at); const auto values = read(merged.resource, at + 8); at += 12; auto& out = rows[key]; for (std::uint32_t value = 0; value < values; ++value, at += 4) out.push_back(read(merged.resource, at)); } return rows; } } // namespace int main() { const auto base = lookup(3, {{1, {0}}}); // One mod adds a row onto a program the game has; another brings programs of its own and // points rows at them (indices 3 and 4, which the base's three records do not have). const auto good = lookup(3, {{1, {0}}, {2, {2}}}); const auto own = lookup(5, {{1, {0}}, {3, {4}}, {4, {1, 3}}, {5, {1}}}); const std::vector edits{good.table(), own.table()}; const auto merged = fb::shader::merge_program_lookup(base.table(), edits); const auto rows = rows_of(merged); check(merged.added == 1 && rows.contains(2) && !rows.contains(5), "Rows onto the base's programs are merged"); check(merged.outside == 3 && !rows.contains(3) && !rows.contains(4), "Rows pointing past the base's records are left out, also when only one of their indices does"); check(rows.size() == 2 && rows.at(1) == std::vector{0}, "The base's own rows stay as they were"); check(read(merged.resourceMeta, 8) == merged.resource.size() - read(merged.resourceMeta, 0), "The lookup size in the metadata matches the merged rows"); for (const auto& [key, values] : rows) for (const auto value : values) check(value < 3, "No merged row points past the base's records"); auto changed = good; changed.resource[4] = std::byte{99}; const std::vector foreign{changed.table()}; const auto refused = fb::shader::merge_program_lookup(base.table(), foreign); check(refused.outside == 1 && !rows_of(refused).contains(2), "Equal record counts cannot establish program identity"); const auto selected = lookup(5, {{1, {0}}, {9, {4}}}); const auto alias = lookup(5, {{1, {0}}, {9, {4}}, {10, {3}}}); const std::vector same{alias.table(), alias.table()}; const auto retained = fb::shader::merge_program_lookup(selected.table(), same); check(retained.added == 1 && retained.conflicts == 0 && rows_of(retained).contains(9), "Selected custom rows survive and duplicate aliases are not conflicts"); check(std::equal(selected.resource.begin(), selected.resource.begin() + 8, retained.resource.begin()), "Opaque program bytes remain unchanged"); check(retained.resource.size() == alias.resource.size(), "Aliases never duplicate program heaps"); const auto stock = preserved_lookup(false, {{1, {0}}}); const auto stockAlias = preserved_lookup(false, {{1, {0}}, {2, {0}}}); const auto extension = preserved_lookup(true, {{1, {0}}, {9, {1}}}); for (const auto bytes : {std::size_t{0}, std::size_t{4}, std::size_t{12}, std::size_t{32}, std::size_t{544}}) { auto truncated = extension; truncated.resource.resize(bytes); check(!fb::shader::preserves_stock_programs(truncated.table(), stock.table()), "A retained table's metadata cannot authorize reads past its actual payload"); } auto shortMeta = extension; shortMeta.meta.resize(8); check(!fb::shader::preserves_stock_programs(shortMeta.table(), stock.table()), "Short retained metadata is refused"); #ifdef _WIN32 SYSTEM_INFO system{}; GetSystemInfo(&system); const auto page = static_cast(system.dwPageSize); auto* guarded = static_cast(VirtualAlloc(nullptr, page * 2, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); check(guarded != nullptr, "Allocate guarded shader-table regression buffer"); if (guarded) { auto* tiny = guarded + page - 12; std::memcpy(tiny, extension.resource.data(), 12); DWORD previous{}; const bool protectedPage = VirtualProtect(guarded + page, page, PAGE_NOACCESS, &previous) != 0; check(protectedPage, "Protect the page immediately after the truncated table"); if (protectedPage) check(!fb::shader::preserves_stock_programs({{tiny, 12}, extension.meta}, stock.table()), "Direct graph selection refuses truncation before touching the guard page"); VirtualFree(guarded, 0, MEM_RELEASE); } #endif const std::vector stockEdits{stockAlias.table()}; check(fb::shader::only_stock_aliases(stockAlias.table(), stock.table()) && !fb::shader::only_stock_aliases(extension.table(), stock.table()), "Selection distinguishes stock aliases from a private graph"); const auto replacement = preserved_lookup(false, {{1, {0, 0}}, {2, {0}}}); check(!fb::shader::only_stock_aliases(replacement.table(), stock.table()), "Changing an original material row is not an alias-only edit"); const auto compatible = fb::shader::merge_program_lookup(extension.table(), stockEdits, stock.table()); check(compatible.added == 1 && compatible.outside == 0 && rows_of(compatible).contains(9), "Verified stock aliases coexist with an appended private program array"); check(std::equal(extension.resource.begin(), extension.resource.begin() + 556, compatible.resource.begin()), "Stock alias merge leaves the selected graph and all relocations untouched"); auto changedHeap = extension; changedHeap.resource[192] = std::byte{8}; check(fb::shader::merge_program_lookup(changedHeap.table(), stockEdits, stock.table()).outside == 1, "A changed stock heap cannot borrow stock aliases"); auto missingFixup = extension; const std::uint32_t other = 0; std::memcpy(missingFixup.resource.data() + 552, &other, 4); check(fb::shader::merge_program_lookup(missingFixup.table(), stockEdits, stock.table()).outside == 1, "A copied record without its relocation cannot borrow stock aliases"); auto extraFixup = extension; const std::uint32_t unexpected = 64, extraSize = 16; const auto insertion = extraFixup.resource.begin() + 556; const auto* encoded = reinterpret_cast(&unexpected); extraFixup.resource.insert(insertion, encoded, encoded + 4); std::memcpy(extraFixup.meta.data() + 4, &extraSize, 4); check(fb::shader::merge_program_lookup(extraFixup.table(), stockEdits, stock.table()).outside == 1, "Extra fixups cannot reinterpret the unchanged stock heap"); const auto invalidAlias = preserved_lookup(false, {{1, {0}}, {2, {1}}}); const std::vector invalidEdits{invalidAlias.table()}; check(fb::shader::merge_program_lookup(extension.table(), invalidEdits, stock.table()).outside == 1, "Stock aliases cannot address the selected graph's private program slots"); check(fb::shader::merge_program_lookup(extension.table(), stockEdits).outside == 1, "The stock exception requires the caller's actual game table"); if (failures) return 1; std::cout << "shader lookup tests passed\n"; return 0; }