From c1464201923f37ca09a09535f6222ccb1ba00ffd Mon Sep 17 00:00:00 2001 From: MyLuxy <240354279+MyLuxy@users.noreply.github.com> Date: Sat, 17 Oct 2026 00:31:32 +0200 Subject: [PATCH] fix: buzzheavier through the browser with its file server captured --- app/game/DownloadTipsDialog.jsx | 2 +- app/state/browser-links.js | 2 ++ app/state/download-queue.js | 2 +- app/state/download-sources.js | 16 +++------------- ps5-react/CHANGELOG.md | 6 ++++++ .../native/ps5/payloads/browser/browser_url.c | 13 +++++++++++++ ps5-react/tools/tests/browser_capture_test.c | 16 ++++++++++++++++ 7 files changed, 42 insertions(+), 15 deletions(-) diff --git a/app/game/DownloadTipsDialog.jsx b/app/game/DownloadTipsDialog.jsx index 106003d..d92cf43 100644 --- a/app/game/DownloadTipsDialog.jsx +++ b/app/game/DownloadTipsDialog.jsx @@ -40,7 +40,7 @@ import { FocusScope, Text, View, useIsFocused } from '@ps5-react/core'; } catch { } if (kind === "vikingfile") notes.push({ tone: "info", icon: "globe", key: "browserViking" }); - else if (kind === "browser" || kind === "datanodes") notes.push({ tone: "info", icon: "globe", key: "browserOther" }); + else if (kind === "browser" || kind === "datanodes" || kind === "buzzheavier") notes.push({ tone: "info", icon: "globe", key: "browserOther" }); const need = requiredFirmware(release); if (need !== null && !isDlc(release)) notes.push({ tone: "info", icon: "cpu", key: "firmware", values: { need: `${need}.xx` } }); return notes; diff --git a/app/state/browser-links.js b/app/state/browser-links.js index bd9c876..ccca3a5 100644 --- a/app/state/browser-links.js +++ b/app/state/browser-links.js @@ -26,6 +26,8 @@ import { webURL } from './download-sources.js'; // browser_url.c holds the same rules. function opaqueFileRoute(host, path) { if (/^a-[a-z0-9-]+\.1fichier\.com$/.test(host)) return /^\/[A-Za-z0-9]{10,256}$/.test(path); + // Buzzheavier's file server, as its Download button's hx-redirect names it. + if (/^[a-z0-9-]+\.buzzheavier\.com$/.test(host) && !host.startsWith("www.")) return /^\/d\/[A-Za-z0-9]{8,32}\?v=[A-Za-z0-9_-]{32,1024}$/.test(path); if (/^tunnel\d+\.dlproxy\.uk$/.test(host)) return /^\/[^/?]{8}\/[^/?]{32,1024}\?sig=[^&]+$/.test(path) && /^[!-~]+$/.test(path) && !path.includes("\\"); return false; } diff --git a/app/state/download-queue.js b/app/state/download-queue.js index 508ae13..ac372ab 100644 --- a/app/state/download-queue.js +++ b/app/state/download-queue.js @@ -207,7 +207,7 @@ import { VIKING_PAGE_PREFIX, VIKING_PREFIX, describeVikingCapture, linkPageMetad } : formats.artifact(release, root2); function reresolve(job2) { const source2 = job2.parts ? job2.currentSource : job2.release; - if ((job2.reresolved || 0) >= 2 || ["direct", "browser", "datanodes", "manifest", "unavailable"].includes(sourceKind(source2))) return false; + if ((job2.reresolved || 0) >= 2 || ["direct", "browser", "datanodes", "buzzheavier", "manifest", "unavailable"].includes(sourceKind(source2))) return false; job2.reresolved = (job2.reresolved || 0) + 1; delete job2.options; if (job2.parts) delete job2.parts[job2.partIndex].options; diff --git a/app/state/download-sources.js b/app/state/download-sources.js index 9a38994..5cfbeb8 100644 --- a/app/state/download-sources.js +++ b/app/state/download-sources.js @@ -127,7 +127,9 @@ if (kind === "unavailable") throw new Error("Source unavailable: no download URL."); if (kind === "direct") return { url: webURL(url).url, rejectHtml: true }; if (kind === "manifest") throw new Error("Split manifests must be resolved by the queue."); - if (kind === "browser" || kind === "datanodes") throw new BrowserRequired(url); + // Buzzheavier's pages sit behind a Cloudflare check only a browser passes (403 to the app, + // 2026-10-17): its file link is captured from the console's browser, as Datanodes' is. + if (kind === "browser" || kind === "datanodes" || kind === "buzzheavier") throw new BrowserRequired(url); if (kind === "filekeeper") return fileKeeper(url, signal); const response = await request(url, { signal }); const base = response.url || url; @@ -136,18 +138,6 @@ let candidate; if (kind === "mediafire") candidate = anchor(html, "downloadButton"); if (kind === "vikingfile") candidate = anchor(html, "download-link"); - if (kind === "buzzheavier") { - const button = (html.match(/<[a-z]+\b[^>]*>/gi) || []).find((tag) => attribute(tag, "hx-get")?.includes("/download")); - const route = button && attribute(button, "hx-get"); - if (route) { - const next = await request(absolute(route, base), { - signal, - followRedirects: false, - headers: { "HX-Request": "true", "HX-Current-URL": base } - }); - candidate = next.headers?.["hx-redirect"] || next.headers?.location; - } - } if (!candidate) throw new BrowserRequired(url); return direct(absolute(candidate, base), signal); } diff --git a/ps5-react/CHANGELOG.md b/ps5-react/CHANGELOG.md index 2135f69..16ef07c 100644 --- a/ps5-react/CHANGELOG.md +++ b/ps5-react/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased — Buzzheavier's file server + +- Anchor mode accepts Buzzheavier's file server, `.buzzheavier.com/d/?v=`, + which its Download button's `hx-redirect` answer names and which carries no file name (observed + 2026-10-17; the file's name comes in its Content-Disposition). Its pages stay on the bare domain. + ## Unreleased — links up to 2047 bytes - A captured link may be up to 2047 bytes (was 543, Orbit Store's limit): FileKeeper hands its file diff --git a/ps5-react/native/ps5/payloads/browser/browser_url.c b/ps5-react/native/ps5/payloads/browser/browser_url.c index 658c809..6dd4ffa 100644 --- a/ps5-react/native/ps5/payloads/browser/browser_url.c +++ b/ps5-react/native/ps5/payloads/browser/browser_url.c @@ -113,6 +113,19 @@ static bool opaque_file_route(const char *host, size_t length, const char *path) if (!token((unsigned char)*at)) return false; return size >= 10 && size <= 256; } + /* Buzzheavier's Download button gets an hx-redirect to its file server, ts.buzzheavier.com/d/ + * ?v=, which names no file: the answer's Content-Disposition does (observed + * 2026-10-17). Its page sits on buzzheavier.com itself, so a subdomain is required. */ + if (host_under(host, length, "buzzheavier.com") && !host_equals(host, length, "buzzheavier.com") && !strncmp(path, "/d/", 3)) { + const char *at = path + 3; + size_t id = 0; + while (token((unsigned char)at[id])) id++; + if (id < 8 || id > 32 || strncmp(at + id, "?v=", 3)) return false; + size_t size = 0; + for (at += id + 3; *at; at++, size++) + if (!token((unsigned char)*at) && *at != '-' && *at != '_') return false; + return size >= 32 && size <= 1024; + } if (host_under(host, length, "dlproxy.uk") && length > 17 && host_equals(host, 6, "tunnel")) { for (size_t i = 6; i < length - 11; i++) if (host[i] < '0' || host[i] > '9') return false; diff --git a/ps5-react/tools/tests/browser_capture_test.c b/ps5-react/tools/tests/browser_capture_test.c index 3a247e5..f22f86d 100644 --- a/ps5-react/tools/tests/browser_capture_test.c +++ b/ps5-react/tools/tests/browser_capture_test.c @@ -197,6 +197,22 @@ int main(void) { assert(!strcmp(longest.url, filekeeper)); } } + // Buzzheavier's file server, as its Download button's hx-redirect answer names it (2026-10-17). + { + const char *buzz = "https://ts.buzzheavier.com/d/vsym6i66c6st?v=mqq2UM8yPmme6XOi01QgF6Qe7xJrqwGMtD34EAS3xFREvk1vXD7bxiLAv94SHzs6D1BV7Ai4tb_mZxkOnBw_Di69Bb2_Wv1KDh0Ns6LgpVFRX0kfPfwrwApCJRYOarUI-OVgeGHpPuBrCdZUhlzldTPZmE-hV9cBy8ALIdmNIodrM648z07x8tS-"; + assert(browser_capture_anchor_url("PPSA16901", buzz) == BROWSER_CAPTURE_UNBOUND); + assert(!browser_capture_anchor_url("PPSA16901", "https://buzzheavier.com/d/vsym6i66c6st?v=mqq2UM8yPmme6XOi01QgF6Qe7xJrqwGMtD34")); + assert(!browser_capture_anchor_url("PPSA16901", "https://buzzheavier.com/vsym6i66c6st")); + assert(!browser_capture_anchor_url("PPSA16901", "https://ts.buzzheavier.com/d/vsym6i66c6st")); + assert(!browser_capture_anchor_url("PPSA16901", "https://ts.buzzheavier.com/d/vsym6i66c6st?v=short")); + assert(!browser_capture_anchor_url("PPSA16901", "https://ts.buzzheavier.com/d/vsym6i66c6st?v=mqq2UM8yPmme6XOi01QgF6Qe7xJrqwGMtD34EAS&x=1")); + assert(!browser_capture_anchor_url("PPSA16901", "https://ts.buzzheavier.com.evil.test/d/vsym6i66c6st?v=mqq2UM8yPmme6XOi01QgF6Qe7xJrqwGMtD34")); + char header[400]; + snprintf(header, sizeof header, "HTTP/1.1 204 No Content\r\nhx-redirect: %s\r\n\r\n", buzz); + BrowserMatch heard = {.prefix = "https://", .filename = "PPSA16901", .anchor = true}; + assert(browser_match(&heard, (const unsigned char *)header, strlen(header))); + assert(!strcmp(heard.url, buzz)); + } // Page mode: the Vikingfile file page a LinkVault vault opens, and nothing else. assert(browser_capture_page_url("https://vikingfile.com/f/jjeEUkyPki")); assert(browser_capture_page_url("https://vik1ngfile.site/f/jjeEUkyPki"));