Merge branch 'Dingo-Shenanigans:main' into main

This commit is contained in:
Jean-Luc T. authored and GitHub committed 2026-10-09 18:36:10 +02:00
commit d2e17db9cb
4 files changed
+102 -67

No files matched your search

+23 -9
View File
@@ -116,17 +116,31 @@ hostent* WSAAPI get_host_by_name(const char* name) {
return original_get_host_by_name.load()(name);
}
// `required`: the entry point the game itself resolves names through; without it nothing is
// blocked, and that is an error. The others close the same door for anything else loaded into
// the process, and are left alone where they cannot be hooked: Wine's ws2_32 (Proton, on Linux
// and the Steam Deck) does not have all of them, and what it does not have nothing can call.
template<class Fn>
void hook(const char* name, void* replacement, std::atomic<Fn>& original) {
void hook(const char* name, void* replacement, std::atomic<Fn>& original, bool required) {
const auto fail = [&](const std::string& why) {
if (required) throw std::runtime_error(why);
logging::log(logging::Level::info, logging::Channel::runtime, "EA online services block: {} left as it is ({}).", name, why);
};
const auto module = LoadLibraryExW(L"ws2_32.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
const auto target = module ? reinterpret_cast<void*>(GetProcAddress(module, name)) : nullptr;
if (!target) throw std::runtime_error(std::string("Missing ") + name);
if (!target) return fail(std::string("Missing ") + name);
void* relay{};
const auto create = hook_prepare(target, replacement, &relay);
if (create != HookOk) throw std::runtime_error(std::string("Cannot hook ") + name + ": " + hook_status_string(create));
if (create != HookOk || !relay) {
if (create == HookOk) (void)hook_remove(target);
return fail(std::string("Cannot hook ") + name + ": " + hook_status_string(create == HookOk ? HookUnsupportedFunction : create));
}
original.store(reinterpret_cast<Fn>(relay));
const auto enable = hook_enable(target);
if (enable != HookOk) throw std::runtime_error(std::string("Cannot enable ") + name + ": " + hook_status_string(enable));
if (enable != HookOk) {
(void)hook_remove(target);
return fail(std::string("Cannot enable ") + name + ": " + hook_status_string(enable));
}
}
} // namespace
@@ -137,11 +151,11 @@ bool start_ea_service_block(std::string& error) noexcept {
// same door for anything else loaded into the process.
#pragma warning(push)
#pragma warning(disable: 4191)
hook("getaddrinfo", reinterpret_cast<void*>(&get_addr_info_a), original_get_addr_info_a);
hook("GetAddrInfoW", reinterpret_cast<void*>(&get_addr_info_w), original_get_addr_info_w);
hook("GetAddrInfoExA", reinterpret_cast<void*>(&get_addr_info_ex_a), original_get_addr_info_ex_a);
hook("GetAddrInfoExW", reinterpret_cast<void*>(&get_addr_info_ex_w), original_get_addr_info_ex_w);
hook("gethostbyname", reinterpret_cast<void*>(&get_host_by_name), original_get_host_by_name);
hook("getaddrinfo", reinterpret_cast<void*>(&get_addr_info_a), original_get_addr_info_a, true);
hook("GetAddrInfoW", reinterpret_cast<void*>(&get_addr_info_w), original_get_addr_info_w, false);
hook("GetAddrInfoExA", reinterpret_cast<void*>(&get_addr_info_ex_a), original_get_addr_info_ex_a, false);
hook("GetAddrInfoExW", reinterpret_cast<void*>(&get_addr_info_ex_w), original_get_addr_info_ex_w, false);
hook("gethostbyname", reinterpret_cast<void*>(&get_host_by_name), original_get_host_by_name, false);
#pragma warning(pop)
error.clear();
return true;
+67 -51
View File
@@ -449,77 +449,96 @@ void draw_command_list(ChatState& c, ImFont* heading, ImFont* body, ImVec2 botto
"; Tab for the next, Shift+Tab back").c_str());
}
// The vote a dedicated server is running, or has just finished: a card above the chat with what
// is voted on, the tally, the time left, and Yes and No. The player answers with their binds
// (shown on the buttons; F1 and F2 unless changed), or by clicking while the cursor is free.
// `bottom_right` is the corner the card sits on. Returns the room it took, 0 when there is none.
float draw_vote(const MultiplayerVote& vote, ImFont* heading, ImFont* body, ImVec2 bottom_right, float width, float scale,
bool clickable) {
if (!vote.id) return 0.0f;
constexpr ImU32 green = IM_COL32(88, 214, 141, 255), red = IM_COL32(236, 96, 96, 255);
// The vote a dedicated server is running, or has just finished: a card on the middle of the
// screen's right edge, in the look of the overlay's notices (a dark tile with a strip of colour
// down its left). It says what is voted on, the tally and the time left, and has Yes and No. The
// player answers with their binds (the keycaps on the buttons; F1 and F2 unless changed), or by
// clicking while the cursor is free.
void draw_vote(const MultiplayerVote& vote, ImFont* heading, ImFont* body, ImVec2 display, float scale, bool clickable) {
if (!vote.id) return;
constexpr ImU32 green = skate_theme::good, red = skate_theme::danger;
const bool running = vote.outcome == 0;
const ImU32 accent = running ? theme::blue : vote.outcome == 1 ? green : vote.outcome == 2 ? red : theme::muted;
const char* title = running ? "VOTE" : vote.outcome == 1 ? "VOTE PASSED" : vote.outcome == 2 ? "VOTE FAILED" : "VOTE CANCELLED";
std::string question = vote.label;
if (!question.empty() && question.front() >= 'a' && question.front() <= 'z') question.front() = static_cast<char>(question.front() - 32);
if (running) question += '?';
const float pad = 12.0f * scale, gap = 6.0f * scale, title_size = 14.0f * scale, size = 16.0f * scale, fine = 14.0f * scale;
const float button_height = 30.0f * scale, bar = 3.0f * scale, inner = width - pad * 2.0f;
const auto question_extent = body->CalcTextSizeA(size, FLT_MAX, inner, question.c_str());
const float width = 360.0f * scale, margin = 24.0f * scale, strip = 5.0f * scale;
const float pad = 14.0f * scale, gap = 8.0f * scale, title_size = 13.0f * scale, size = 18.0f * scale, fine = 13.0f * scale;
const float button_height = 34.0f * scale, bar = 4.0f * scale, inner = width - strip - pad * 2.0f;
const auto question_extent = heading->CalcTextSizeA(size, FLT_MAX, inner, question.c_str());
const bool answers = running && vote.may_vote;
const float height = pad + title_size + gap + question_extent.y + gap + fine + gap + bar +
const float height = pad + title_size + gap + question_extent.y + gap + bar + gap + fine +
(running ? gap + (answers ? button_height : fine) : 0.0f) + pad;
const ImVec2 min(bottom_right.x - width, bottom_right.y - height), max(bottom_right);
const ImVec2 min(display.x - margin - width, std::floor((display.y - height) * 0.5f)), max(min.x + width, min.y + height);
auto* draw = ImGui::GetForegroundDrawList();
draw->AddRectFilled(min, max, with_alpha(theme::ink, 0.92f), 4.0f * scale);
draw->AddRect(min, max, with_alpha(accent, 0.9f), 4.0f * scale);
draw->AddRectFilled(min, max, with_alpha(theme::ink, 0.92f));
draw->AddRectFilled(min, ImVec2(min.x + strip, max.y), accent);
const float left = min.x + strip + pad, right = max.x - pad;
float y = min.y + pad;
draw->AddText(heading, title_size, ImVec2(min.x + pad, y), accent, title);
draw->AddText(heading, title_size, ImVec2(left, y), accent, title);
if (running) {
const auto left = std::to_string(vote.seconds) + " s";
const auto extent = heading->CalcTextSizeA(title_size, FLT_MAX, 0.0f, left.c_str());
draw->AddText(heading, title_size, ImVec2(max.x - pad - extent.x, y), theme::muted, left.c_str());
const auto time = std::to_string(vote.seconds) + " s";
const auto extent = heading->CalcTextSizeA(title_size, FLT_MAX, 0.0f, time.c_str());
draw->AddText(heading, title_size, ImVec2(right - extent.x, y), theme::paper, time.c_str());
}
y += title_size + gap;
draw->AddText(body, size, ImVec2(min.x + pad, y), theme::paper, question.c_str(), nullptr, inner);
draw->AddText(heading, size, ImVec2(left, y), theme::paper, question.c_str(), nullptr, inner);
y += question_extent.y + gap;
// The tally, and a bar of how far Yes is towards what it needs.
float x = min.x + pad;
// How far Yes is towards what it needs, then the tally under it.
const float share = vote.needed ? std::clamp(static_cast<float>(vote.yes) / static_cast<float>(vote.needed), 0.0f, 1.0f) : 0.0f;
draw->AddRectFilled(ImVec2(left, y), ImVec2(right, y + bar), skate_theme::tile_light);
if (share > 0.0f) draw->AddRectFilled(ImVec2(left, y), ImVec2(left + inner * share, y + bar), green);
y += bar + gap;
float x = left;
const auto word = [&](const std::string& text, ImU32 colour) {
draw->AddText(heading, fine, ImVec2(x, y), colour, text.c_str());
x += heading->CalcTextSizeA(fine, FLT_MAX, 0.0f, text.c_str()).x + 14.0f * scale;
draw->AddText(body, fine, ImVec2(x, y), colour, text.c_str());
x += body->CalcTextSizeA(fine, FLT_MAX, 0.0f, text.c_str()).x + 14.0f * scale;
};
word("Yes " + std::to_string(vote.yes), green);
word("No " + std::to_string(vote.no), red);
word(std::to_string(vote.needed) + " needed", theme::muted);
y += fine + gap;
const float share = vote.needed ? std::clamp(static_cast<float>(vote.yes) / static_cast<float>(vote.needed), 0.0f, 1.0f) : 0.0f;
draw->AddRectFilled(ImVec2(min.x + pad, y), ImVec2(max.x - pad, y + bar), with_alpha(theme::muted, 0.35f), bar * 0.5f);
if (share > 0.0f) draw->AddRectFilled(ImVec2(min.x + pad, y), ImVec2(min.x + pad + inner * share, y + bar), green, bar * 0.5f);
y += bar;
if (!running) return height + gap;
{
const auto needed = std::to_string(vote.needed) + " needed";
const auto extent = body->CalcTextSizeA(fine, FLT_MAX, 0.0f, needed.c_str());
draw->AddText(body, fine, ImVec2(right - extent.x, y), theme::muted, needed.c_str());
}
y += fine;
if (!running) return;
y += gap;
if (!answers) {
draw->AddText(body, fine, ImVec2(min.x + pad, y), theme::muted, "This vote is about you, so you have no vote in it.");
return height + gap;
draw->AddText(body, fine, ImVec2(left, y), theme::muted, "This vote is about you, so you have no vote in it.");
return;
}
// Two tiles as the menu's: grey, the answer's colour once it is this player's, each with its
// bind on a white keycap.
const float button_width = (inner - gap) * 0.5f;
const auto button = [&](float left, bool yes, std::uint32_t bind) {
const ImVec2 a(left, y), b(left + button_width, y + button_height);
const auto button = [&](float at, bool yes, std::uint32_t bind) {
const ImVec2 a(at, y), b(at + button_width, y + button_height);
const ImU32 colour = yes ? green : red;
const bool chosen = vote.mine == (yes ? 1 : 2);
const bool hovered = clickable && ImGui::IsMouseHoveringRect(a, b, false);
draw->AddRectFilled(a, b, with_alpha(colour, chosen ? 0.85f : hovered ? 0.35f : 0.14f), 4.0f * scale);
draw->AddRect(a, b, with_alpha(colour, 0.9f), 4.0f * scale);
const std::string label = (bind ? dingosdk::controller_combo_label(bind) + " " : std::string{}) + (yes ? "Yes" : "No");
const auto extent = heading->CalcTextSizeA(size, FLT_MAX, 0.0f, label.c_str());
draw->AddText(heading, size, ImVec2(a.x + (button_width - extent.x) * 0.5f, a.y + (button_height - extent.y) * 0.5f),
chosen ? theme::ink : theme::paper, label.c_str());
draw->AddRectFilled(a, b, chosen ? colour : hovered ? skate_theme::tile_light : skate_theme::tile_grey);
if (!chosen) draw->AddRectFilled(ImVec2(a.x, b.y - 3.0f * scale), b, colour);
const std::string key = bind ? dingosdk::controller_combo_label(bind) : std::string{};
const char* label = yes ? "YES" : "NO";
const float label_size = 15.0f * scale, key_size = 12.0f * scale, cap_height = 20.0f * scale;
const auto label_extent = heading->CalcTextSizeA(label_size, FLT_MAX, 0.0f, label);
const auto key_extent = key.empty() ? ImVec2{} : heading->CalcTextSizeA(key_size, FLT_MAX, 0.0f, key.c_str());
const float cap_width = key.empty() ? 0.0f : key_extent.x + 12.0f * scale, between = key.empty() ? 0.0f : 8.0f * scale;
float cursor = a.x + (button_width - cap_width - between - label_extent.x) * 0.5f;
if (!key.empty()) {
const ImVec2 cap(cursor, a.y + (button_height - cap_height) * 0.5f);
draw->AddRectFilled(cap, ImVec2(cap.x + cap_width, cap.y + cap_height), theme::paper, 3.0f * scale);
draw->AddText(heading, key_size, ImVec2(cap.x + 6.0f * scale, cap.y + (cap_height - key_extent.y) * 0.5f), skate_theme::black,
key.c_str());
cursor += cap_width + between;
}
draw->AddText(heading, label_size, ImVec2(cursor, a.y + (button_height - label_extent.y) * 0.5f),
chosen ? skate_theme::black : theme::paper, label);
if (hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) queue_multiplayer_action("vote", yes ? "yes" : "no");
};
button(min.x + pad, true, vote.yes_bind);
button(min.x + pad + button_width + gap, false, vote.no_bind);
return height + gap;
button(left, true, vote.yes_bind);
button(left + button_width + gap, false, vote.no_bind);
}
void draw_chat() {
@@ -637,11 +656,10 @@ void draw_chat() {
ImGui::End();
ImGui::PopStyleVar(2);
ImGui::PopStyleColor(4);
// The vote card sits on the panel; the command list and the emote picker go above it.
const float above = panel_top - 6.0f * scale -
draw_vote(c.feed.vote, heading, body, ImVec2(display.x - margin, panel_top - 6.0f * scale), width, scale, true);
const float above = panel_top - 6.0f * scale;
draw_command_list(c, heading, body, ImVec2(display.x - margin - width, above), width, scale);
emote_picker(c, heading, ImVec2(display.x - margin - width, above), width, scale);
draw_vote(c.feed.vote, heading, body, display, scale, true); // the cursor is free while typing
return;
}
@@ -692,9 +710,7 @@ void draw_chat() {
else draw->AddText(body, size, text_at, text, drawn.text.c_str(), nullptr, wrap);
bottom = top_left.y - gap;
}
// Above the newest lines, or in the corner when there are none. Clickable while the menu or
// the console has freed the cursor.
draw_vote(c.feed.vote, heading, body, ImVec2(display.x - margin, bottom), width, scale,
s.visible.load() || s.console_visible.load());
// Clickable while the menu or the console has freed the cursor.
draw_vote(c.feed.vote, heading, body, display, scale, s.visible.load() || s.console_visible.load());
}
} // namespace dingosdk::overlay::detail
+10 -5
View File
@@ -139,7 +139,7 @@ void camera_controls(SkateMenu& menu, const Model& model, const CallbacksV3& cal
void movement_controls(SkateMenu& menu, const Model& model, const CallbacksV3& callbacks) {
const auto& debug = model.debug;
begin_card(menu, "movement", "MOVEMENT");
begin_card(menu, "movement", "FLIGHT & BAILS");
bool noclip = debug.noclip;
if (toggle_row(menu, "Noclip", "Fly with the normal player camera. Includes No Bail; uses the Freecam flight speed.", noclip,
(debug.noclip_available || debug.noclip) && callbacks.queue_debug))
@@ -155,9 +155,14 @@ void movement_controls(SkateMenu& menu, const Model& model, const CallbacksV3& c
(debug.no_bail_available || debug.no_bail) && callbacks.queue_debug))
debug_request(menu, callbacks, {DebugAction::set_no_bail, no_bail});
bool hall_of_meat = model.hall_of_meat.enabled;
if (toggle_row(menu, "Hall of Meat",
"Your bails show the bones they hurt and score a Meat card, as in skate. 3. A break slows the game in single player.",
hall_of_meat, model.hall_of_meat.available && callbacks.queue_console_command))
// No Bail (and noclip, which includes it) stops the bails Hall of Meat scores.
const bool bails_off = debug.no_bail || debug.no_bail_active;
const char* meat_help = hall_of_meat && bails_off
? "On, but No Bail is stopping your bails, so nothing shows. Turn No Bail (and noclip) off to use it."
: bails_off
? "Your bails show the bones they hurt and score a Meat card. Needs No Bail (and noclip) off."
: "Your bails show the bones they hurt and score a Meat card. A break slows the game in single player.";
if (toggle_row(menu, "Hall of Meat", meat_help, hall_of_meat, model.hall_of_meat.available && callbacks.queue_console_command))
send_console(menu, callbacks, hall_of_meat ? "hallofmeat 1" : "hallofmeat 0");
end_card();
@@ -182,7 +187,7 @@ void movement_controls(SkateMenu& menu, const Model& model, const CallbacksV3& c
}
void skater_page(SkateMenu& menu, const Model& model, const CallbacksV3& callbacks) {
category_tabs(menu, menu.skater_tab, {"CAMERA", "MOVEMENT"}, "skater-tabs");
category_tabs(menu, menu.skater_tab, {"CAMERA", "GAMEPLAY"}, "skater-tabs");
ImGui::PushID(menu.skater_tab);
ImGui::BeginChild("skater-tab", ImVec2(0, page_body_height(menu)));
if (menu.skater_tab == 0) camera_controls(menu, model, callbacks);
+2 -2
View File
@@ -164,7 +164,7 @@ void Host::start_vote(Guest &guest, VoteKind kind, std::string_view argument) {
const auto label = vote.label;
vote_ = std::move(vote);
send_chat(guest_name(guest) + " started a vote to " + label + " (" + std::to_string(setting.percent) + "% needed, " +
std::to_string(config_.votes.seconds) + " s). Vote on the card above chat, or type /yes or /no.");
std::to_string(config_.votes.seconds) + " s). Vote on the card at the right of your screen, or type /yes or /no.");
log_("[vote] " + guest_name(guest) + " started a vote to " + label + ".");
check_vote(false);
}
@@ -228,7 +228,7 @@ void Host::check_vote(bool expired) {
const bool lost = !passed && (expired || yes + (eligible - std::min(eligible, yes + no)) < needed);
const auto tally = std::to_string(yes) + " yes, " + std::to_string(no) + " no, " + std::to_string(needed) + " needed";
if (!passed && !lost) {
// Games show the tally as it changes (the vote card above chat); chat is not filled with it.
// Games show the tally as it changes (the vote card); chat is not filled with it.
vote.shown_yes = yes;
vote.shown_no = no;
show_vote(vote, multiplayer::vote_running, yes, no, needed);