From 4d77b859bcf7711bb778e50085d77bb2e76f7dbb Mon Sep 17 00:00:00 2001 From: AyeZeeBB Date: Fri, 9 Oct 2026 11:26:10 -0500 Subject: [PATCH] Start under Proton again; vote card on the right in the notices' look; Hall of Meat note Linux and Steam Deck. 2.0.0 did not start under Proton: the launcher said "ReSkate could not attach to Skate". The block on EA's online services hooks five of Windows' name lookups and took any failure as fatal, and Wine's ws2_32 has no GetAddrInfoExA. Only getaddrinfo, the one Skate.exe resolves names through, is required now. The other four are extra cover for anything else in the process: one that is missing or cannot be hooked is left as it is, with a line in the log. A function the system does not have cannot be called, so nothing is left unblocked. On Windows all five are hooked as before. Vote card. It sat on top of chat and looked like neither the menu nor the notices. It is now on the middle of the screen's right edge, whatever chat is doing, as a flat dark tile with a strip of colour down its left: blue while running, green passed, red failed, grey cancelled. The question is in the heading font, the bar is under it with the tally below, and Yes and No are menu tiles with their binds on white keycaps. The server's line that starts a vote says "the card at the right of your screen". Hall of Meat. Its row in the Insert overlay now says when No Bail (or noclip, which includes it) is stopping the bails it scores. The tab it is on, MOVEMENT, is now GAMEPLAY, and the card FLIGHT & BAILS. --- Extension/Boot/ea_service_block.cpp | 32 ++++-- Extension/UI/Overlay/chat_overlay.cpp | 118 ++++++++++++--------- Extension/UI/Overlay/skate_menu_skater.cpp | 15 ++- Server/server_votes.cpp | 4 +- 4 files changed, 102 insertions(+), 67 deletions(-) diff --git a/Extension/Boot/ea_service_block.cpp b/Extension/Boot/ea_service_block.cpp index 8947c55..4807875 100644 --- a/Extension/Boot/ea_service_block.cpp +++ b/Extension/Boot/ea_service_block.cpp @@ -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 -void hook(const char* name, void* replacement, std::atomic& original) { +void hook(const char* name, void* replacement, std::atomic& 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(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(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(&get_addr_info_a), original_get_addr_info_a); - hook("GetAddrInfoW", reinterpret_cast(&get_addr_info_w), original_get_addr_info_w); - hook("GetAddrInfoExA", reinterpret_cast(&get_addr_info_ex_a), original_get_addr_info_ex_a); - hook("GetAddrInfoExW", reinterpret_cast(&get_addr_info_ex_w), original_get_addr_info_ex_w); - hook("gethostbyname", reinterpret_cast(&get_host_by_name), original_get_host_by_name); + hook("getaddrinfo", reinterpret_cast(&get_addr_info_a), original_get_addr_info_a, true); + hook("GetAddrInfoW", reinterpret_cast(&get_addr_info_w), original_get_addr_info_w, false); + hook("GetAddrInfoExA", reinterpret_cast(&get_addr_info_ex_a), original_get_addr_info_ex_a, false); + hook("GetAddrInfoExW", reinterpret_cast(&get_addr_info_ex_w), original_get_addr_info_ex_w, false); + hook("gethostbyname", reinterpret_cast(&get_host_by_name), original_get_host_by_name, false); #pragma warning(pop) error.clear(); return true; diff --git a/Extension/UI/Overlay/chat_overlay.cpp b/Extension/UI/Overlay/chat_overlay.cpp index 6f27652..c2cdf59 100644 --- a/Extension/UI/Overlay/chat_overlay.cpp +++ b/Extension/UI/Overlay/chat_overlay.cpp @@ -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(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(vote.yes) / static_cast(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(vote.yes) / static_cast(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 diff --git a/Extension/UI/Overlay/skate_menu_skater.cpp b/Extension/UI/Overlay/skate_menu_skater.cpp index d7a4d47..5609442 100644 --- a/Extension/UI/Overlay/skate_menu_skater.cpp +++ b/Extension/UI/Overlay/skate_menu_skater.cpp @@ -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); diff --git a/Server/server_votes.cpp b/Server/server_votes.cpp index b18c611..75bc200 100644 --- a/Server/server_votes.cpp +++ b/Server/server_votes.cpp @@ -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);