Files
ReSkate/Extension/Plugins/plugin_discovery.cpp
T
AyeZeeBB d884c1c161 Plugins: the rest of the core set (overlay, menu panels, sounds, mods, session and messages)
- overlay: reskate.on("draw", function(d) ...) each tick, with text, rect, line, circle, poly
  and world_text, world_line, world_circle (projected through the camera as the frame draws),
  size and world_to_screen; 4000 shapes a frame. reskate.ui.toast through ReSkate's notices, three
  in 5 s. Images are left for later: they need texture slots in the overlay's renderer.
- menu: reskate.menu.panel(title, build) on a new PLUGINS tab of the ADDONS page, with heading,
  label, separator, button, toggle, slider and dropdown, built ten times a second while it shows.
- audio.ui: the game's menu sounds by name, ten a second. mods.read: installed(name) and list(),
  from the discovery thread's scan.
- session.read: active, is_dedicated, players, me, host, and player_joined / player_left.
  net.session: send(to, message) to the same plugin on other games (by its manifest.json name) in
  the session's throwdown packets, tagged 0xD1 (plugin_wire.h); 4 KB each, 16 KB a second.
- The overlay and the host share plugin_ui.h (shapes, panels, actions) and plugin_camera.h.
- Fixed: the overlay asked ImGui for the window's size before its context existed, which crashed
  on the first frames; it now reads the size from the context.
- reskate-test has overlay (ptest hud), menu, audio, mods and session (ptest ping) modules;
  reskate-hostile has draw-flood, toast-spam, sound-spam, message-flood and panel-loop.

Checked in game: ptest all 62 passed, 0 failed; the test HUD, the PLUGINS tab and its panel; every
hostile attack stopped or refused, trick-loop and panel-loop included. Not yet: ptest ping with a
second player.
2026-10-19 13:20:43 -05:00

70 lines
3.1 KiB
C++

#include "plugin_discovery.h"
#include "plugin_manifest.h"
#include "Engine/Vfs/mod_list.h"
#include <algorithm>
#include <array>
#include <fstream>
namespace dingosdk::plugins {
namespace {
constexpr std::array<std::string_view, 22> runnable{".exe", ".dll", ".sys", ".scr", ".com", ".cpl", ".msi", ".msp",
".bat", ".cmd", ".ps1", ".psm1", ".vbs", ".vbe", ".js", ".jse", ".wsf", ".wsh", ".hta", ".lnk", ".pif", ".reg"};
constexpr std::size_t most_files = 20000;
// path::string() throws on names outside the ANSI code page; a name here is only shown or compared.
std::string ascii_lowercase(const std::filesystem::path& path) {
std::string text;
for (const auto c : path.generic_wstring())
text += c < 0x80 ? static_cast<char>(c >= L'A' && c <= L'Z' ? c + 32 : c) : '?';
return text;
}
}
std::string check_package_files(const std::filesystem::path& directory) {
std::error_code code;
std::size_t files = 0;
for (std::filesystem::recursive_directory_iterator it(directory, std::filesystem::directory_options::skip_permission_denied, code), end;
!code && it != end; it.increment(code)) {
std::error_code entry_code;
if (!it->is_regular_file(entry_code)) continue;
if (++files > most_files) return "it holds more than 20000 files";
const auto shown = ascii_lowercase(std::filesystem::relative(it->path(), directory, entry_code));
const auto extension = ascii_lowercase(it->path().extension());
bool program = std::find(runnable.begin(), runnable.end(), extension) != runnable.end();
if (!program) {
std::ifstream in(it->path(), std::ios::binary);
char magic[2]{};
program = in.read(magic, 2) && magic[0] == 'M' && magic[1] == 'Z';
}
if (program) return "it holds " + shown + ", a program or a script Windows runs. A plugin never ships one";
}
return {};
}
std::vector<Candidate> discover(const std::filesystem::path& data_root, FileChecks& checks, std::vector<ModInfo>* mods) {
std::vector<Candidate> result;
const auto list = mods::scan_mods(data_root);
// A mods.json the game cannot read loads no mod at all, plugins included.
if (!list.present || !list.issue.empty()) return result;
FileChecks seen;
if (mods) {
mods->clear();
for (const auto& entry : list.entries) mods->push_back({entry.mod.name, entry.mod.title, entry.enabled});
}
for (const auto& entry : list.entries) {
if (!entry.enabled) continue;
const auto& directory = entry.mod.directory;
std::error_code code;
if (!std::filesystem::is_regular_file(directory / manifest_file, code)) continue;
Candidate candidate{entry.mod.name, directory, mods::mod_fingerprint(directory), {}};
const auto key = candidate.id + "|" + candidate.fingerprint;
const auto known = checks.find(key);
candidate.refusal = known != checks.end() ? known->second : check_package_files(directory);
seen.emplace(key, candidate.refusal);
result.push_back(std::move(candidate));
}
checks = std::move(seen);
return result;
}
}