Created
August 16, 2026 17:37
-
-
Save HenkPoley/c58460dec46b0077192e6d5c75317413 to your computer and use it in GitHub Desktop.
Open https://www.nvidia.com/Download/Find.aspx/ - Right-click > inspect element, past this into the JavaScript 'console' tab command line.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * NVIDIA legacy Advanced Driver Search repair + release-branch browser. | |
| * Paste this entire file into the console on: | |
| * https://www.nvidia.com/Download/Find.aspx/ | |
| */ | |
| (async () => { | |
| "use strict"; | |
| const SCRIPT_VERSION = "1.1.0"; | |
| const DOWNLOAD_ROOT = "/Download/"; | |
| const DRIVER_API = | |
| "https://gfwsl.geforce.com/services_toolkit/services/com/nvidia/services/AjaxDriverService.php"; | |
| const KNOWN_RELEASES = [ | |
| 304, 310, 319, 325, 331, 334, 337, 340, 343, 346, | |
| 349, 352, 355, 358, 361, 364, 367, 370, 375, 378, | |
| 381, 384, 387, 390, 396, 410, 415, 418, 430, 435, | |
| 440, 450, 455, 460, 465, 470, 495, 510, 515, 520, | |
| 525, 530, 535, 545, 550, 555, 560, 565, 570, 575, | |
| 580, 590, 595, 610 | |
| ]; | |
| const MAX_HISTORICAL_GAP = Math.max( | |
| ...KNOWN_RELEASES.slice(1).map( | |
| (release, index) => | |
| release - KNOWN_RELEASES[index] | |
| ) | |
| ); | |
| const FUTURE_MISS_PADDING = 10; | |
| const FUTURE_MISS_LIMIT = | |
| MAX_HISTORICAL_GAP + FUTURE_MISS_PADDING; | |
| const SCAN_CONCURRENCY = 6; | |
| const INITIAL_FULL_RESULT_LIMIT = 50; | |
| const MAX_FULL_RESULT_LIMIT = 800; | |
| if ( | |
| !/^\/Download\/Find\.aspx\/?$/i.test( | |
| location.pathname | |
| ) | |
| ) { | |
| throw new Error( | |
| "Open https://www.nvidia.com/Download/Find.aspx/ before running this script." | |
| ); | |
| } | |
| console.clear(); | |
| console.info( | |
| `Installing NVIDIA driver-search fix ${SCRIPT_VERSION}...` | |
| ); | |
| /* | |
| * Remove older versions of this patch. | |
| */ | |
| window.nvDriverArchive?.destroy?.(); | |
| document | |
| .getElementById("nv-release-panel") | |
| ?.remove(); | |
| document | |
| .getElementById("nv-search-50") | |
| ?.remove(); | |
| /* | |
| * Repair URLs that NVIDIA intended to resolve underneath /Download/. | |
| */ | |
| let base = document.querySelector("base"); | |
| if (!base) { | |
| base = document.createElement("base"); | |
| document.head.prepend(base); | |
| } | |
| base.href = new URL( | |
| DOWNLOAD_ROOT, | |
| location.origin | |
| ).href; | |
| base.dataset.nvDriverFix = | |
| SCRIPT_VERSION; | |
| const $ = window.jQuery; | |
| if (!$?.ajaxPrefilter) { | |
| throw new Error( | |
| "The NVIDIA page's jQuery installation is unavailable." | |
| ); | |
| } | |
| if ( | |
| !window.__nvDriverPathPrefilterInstalled | |
| ) { | |
| $.ajaxPrefilter(options => { | |
| if (!options.url) { | |
| return; | |
| } | |
| let url; | |
| try { | |
| url = new URL( | |
| String(options.url), | |
| document.baseURI | |
| ); | |
| } catch { | |
| return; | |
| } | |
| if ( | |
| url.origin !== location.origin | |
| ) { | |
| return; | |
| } | |
| url.pathname = | |
| url.pathname.replace( | |
| /^\/Download\/Find\.aspx\/+/i, | |
| DOWNLOAD_ROOT | |
| ); | |
| options.url = url.href; | |
| }); | |
| window.__nvDriverPathPrefilterInstalled = | |
| true; | |
| } | |
| /* | |
| * Reload the two scripts that failed during the original page load. | |
| */ | |
| const loadMissingScript = ( | |
| id, | |
| path, | |
| isReady | |
| ) => { | |
| if (isReady()) { | |
| return Promise.resolve(); | |
| } | |
| document | |
| .getElementById(id) | |
| ?.remove(); | |
| return new Promise( | |
| (resolve, reject) => { | |
| const script = | |
| document.createElement("script"); | |
| script.id = id; | |
| script.src = new URL( | |
| path, | |
| location.origin | |
| ).href; | |
| script.onload = () => { | |
| if (isReady()) { | |
| resolve(); | |
| } else { | |
| reject( | |
| new Error( | |
| `${path} loaded without defining its functions.` | |
| ) | |
| ); | |
| } | |
| }; | |
| script.onerror = () => { | |
| reject( | |
| new Error( | |
| `Could not load ${path}.` | |
| ) | |
| ); | |
| }; | |
| document.head.append(script); | |
| } | |
| ); | |
| }; | |
| await loadMissingScript( | |
| "nv-driver-fixed-common-js", | |
| "/Download/Scripts/Common.js", | |
| () => | |
| typeof window.showDiv === | |
| "function" && | |
| typeof window.hideDiv === | |
| "function" && | |
| typeof window.isEmpty === | |
| "function" | |
| ); | |
| await loadMissingScript( | |
| "nv-driver-fixed-product-js", | |
| "/Download/Scripts/product.js", | |
| () => | |
| typeof window.GeForce !== | |
| "undefined" && | |
| typeof window.Quadro !== | |
| "undefined" | |
| ); | |
| const byId = id => | |
| document.getElementById(id); | |
| const controls = { | |
| productType: | |
| byId("selProductSeriesType"), | |
| series: | |
| byId("selProductSeries"), | |
| product: | |
| byId("selProductFamily"), | |
| os: | |
| byId("selOperatingSystem"), | |
| driverType: | |
| byId("selDownloadTypeDch"), | |
| driverTypeContainer: | |
| byId("divDownloadTypeDch"), | |
| channel: | |
| byId("ddWHQL"), | |
| language: | |
| byId("ddLanguage"), | |
| cuda: | |
| byId("selCudaToolkitVersion") | |
| }; | |
| const optionalControls = new Set([ | |
| "driverType", | |
| "driverTypeContainer", | |
| "cuda" | |
| ]); | |
| for ( | |
| const [name, control] | |
| of Object.entries(controls) | |
| ) { | |
| if ( | |
| !control && | |
| !optionalControls.has(name) | |
| ) { | |
| throw new Error( | |
| `Required NVIDIA form control is missing: ${name}` | |
| ); | |
| } | |
| } | |
| /* | |
| * Restart NVIDIA's initialization only when Product Series is still empty. | |
| */ | |
| if ( | |
| !controls.series.options.length | |
| ) { | |
| if ( | |
| typeof window.init === "function" | |
| ) { | |
| window.init(); | |
| } else { | |
| window.changeProductSeriesType( | |
| controls.productType.value | |
| ); | |
| } | |
| } | |
| const selectedText = select => | |
| select | |
| ?.selectedOptions?.[0] | |
| ?.textContent | |
| ?.trim() || ""; | |
| const selectedOsName = () => | |
| controls.os | |
| .selectedOptions?.[0] | |
| ?.getAttribute("Name") || | |
| selectedText(controls.os); | |
| const supportsWindowsDriverType = | |
| () => { | |
| const supportedProductTypes = | |
| new Set([ | |
| "1", // GeForce | |
| "3", // NVIDIA RTX / Quadro | |
| "7", // Data Center / Tesla | |
| "11" // TITAN | |
| ]); | |
| const supportedOsNames = | |
| new Set([ | |
| "Windows 10 64-bit", | |
| "Windows 11", | |
| "Windows Server 2016", | |
| "Windows Server 2019", | |
| "Windows Server 2022" | |
| ]); | |
| return ( | |
| supportedProductTypes.has( | |
| controls.productType.value | |
| ) && | |
| supportedOsNames.has( | |
| selectedOsName() | |
| ) | |
| ); | |
| }; | |
| const syncDriverTypeVisibility = | |
| () => { | |
| if ( | |
| !controls.driverTypeContainer | |
| ) { | |
| return; | |
| } | |
| controls | |
| .driverTypeContainer | |
| .style | |
| .display = | |
| supportsWindowsDriverType() | |
| ? "" | |
| : "none"; | |
| }; | |
| syncDriverTypeVisibility(); | |
| /* | |
| * Styling for the archive interface. | |
| */ | |
| byId( | |
| "nv-driver-archive-style" | |
| )?.remove(); | |
| const style = | |
| document.createElement("style"); | |
| style.id = | |
| "nv-driver-archive-style"; | |
| style.textContent = ` | |
| #nv-driver-archive { | |
| box-sizing: border-box; | |
| margin: 12px 0; | |
| padding: 12px; | |
| border: 1px solid #777; | |
| background: #f4f4f4; | |
| color: #111; | |
| font: 13px/1.4 Arial, sans-serif; | |
| } | |
| #nv-driver-archive * { | |
| box-sizing: border-box; | |
| } | |
| #nv-driver-archive .nv-title { | |
| margin-bottom: 8px; | |
| font-size: 15px; | |
| font-weight: bold; | |
| } | |
| #nv-driver-archive .nv-controls { | |
| display: flex; | |
| flex-wrap: wrap; | |
| align-items: end; | |
| gap: 8px; | |
| } | |
| #nv-driver-archive label { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 3px; | |
| font-weight: bold; | |
| } | |
| #nv-driver-archive input, | |
| #nv-driver-archive select, | |
| #nv-driver-archive button { | |
| min-height: 30px; | |
| padding: 4px 8px; | |
| font: inherit; | |
| } | |
| #nv-driver-archive input { | |
| width: 105px; | |
| } | |
| #nv-driver-archive select { | |
| min-width: 210px; | |
| } | |
| #nv-driver-archive button { | |
| cursor: pointer; | |
| } | |
| #nv-driver-archive button:disabled { | |
| cursor: default; | |
| opacity: 0.55; | |
| } | |
| #nv-driver-status { | |
| margin-top: 9px; | |
| min-height: 19px; | |
| } | |
| #nv-driver-results { | |
| margin-top: 10px; | |
| overflow-x: auto; | |
| } | |
| #nv-driver-results table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| background: #fff; | |
| } | |
| #nv-driver-results th, | |
| #nv-driver-results td { | |
| padding: 6px; | |
| border: 1px solid #bbb; | |
| text-align: left; | |
| vertical-align: top; | |
| white-space: nowrap; | |
| } | |
| #nv-driver-results th { | |
| background: #ddd; | |
| } | |
| #nv-driver-results td.nv-name { | |
| min-width: 190px; | |
| white-space: normal; | |
| } | |
| #nv-driver-results a { | |
| color: #0645ad; | |
| } | |
| #nv-driver-results .nv-links { | |
| display: flex; | |
| gap: 8px; | |
| } | |
| `; | |
| document.head.append(style); | |
| /* | |
| * The native select is intentional. Safari filtered the previous datalist | |
| * against its current value, making only R610 appear to be available. | |
| */ | |
| const panel = | |
| document.createElement("section"); | |
| panel.id = "nv-driver-archive"; | |
| panel.innerHTML = ` | |
| <div class="nv-title"> | |
| Release branch archive | |
| </div> | |
| <div class="nv-controls"> | |
| <button | |
| type="button" | |
| id="nv-scan-branches" | |
| > | |
| Find available branches | |
| </button> | |
| <label> | |
| Available branches | |
| <select id="nv-release-options"> | |
| <option value=""> | |
| Scan branches first | |
| </option> | |
| </select> | |
| </label> | |
| <label> | |
| Branch to list | |
| <input | |
| type="number" | |
| min="1" | |
| step="1" | |
| id="nv-release-branch" | |
| placeholder="e.g. 580" | |
| > | |
| </label> | |
| <button | |
| type="button" | |
| id="nv-list-branch" | |
| > | |
| List every driver in branch | |
| </button> | |
| </div> | |
| <div id="nv-driver-status"> | |
| Select the product and OS above, then scan or enter a branch directly. | |
| </div> | |
| <div id="nv-driver-results"></div> | |
| `; | |
| const oldSearchLink = [ | |
| ...document.querySelectorAll("a") | |
| ].find(anchor => | |
| /javascript:\s*search\s*\(\s*\)/i.test( | |
| anchor.getAttribute("href") || "" | |
| ) | |
| ); | |
| const insertionPoint = | |
| oldSearchLink?.closest("table"); | |
| if (insertionPoint) { | |
| insertionPoint.insertAdjacentElement( | |
| "afterend", | |
| panel | |
| ); | |
| } else { | |
| byId("tdSearchResults") | |
| ?.insertAdjacentElement( | |
| "beforebegin", | |
| panel | |
| ); | |
| } | |
| const scanButton = | |
| byId("nv-scan-branches"); | |
| const listButton = | |
| byId("nv-list-branch"); | |
| const branchInput = | |
| byId("nv-release-branch"); | |
| const branchOptions = | |
| byId("nv-release-options"); | |
| const status = | |
| byId("nv-driver-status"); | |
| const results = | |
| byId("nv-driver-results"); | |
| let activeController = null; | |
| let selectionRevision = 0; | |
| let discoveredBranches = []; | |
| const listeners = []; | |
| const setStatus = message => { | |
| status.textContent = message; | |
| }; | |
| const decode = value => { | |
| if (value == null) { | |
| return ""; | |
| } | |
| try { | |
| return decodeURIComponent( | |
| String(value).replace( | |
| /\+/g, | |
| "%20" | |
| ) | |
| ); | |
| } catch { | |
| return String(value); | |
| } | |
| }; | |
| const validDownloadRows = | |
| data => { | |
| const rows = | |
| Array.isArray(data?.IDS) | |
| ? data.IDS | |
| : []; | |
| return rows | |
| .map( | |
| item => | |
| item?.downloadInfo | |
| ) | |
| .filter( | |
| info => | |
| info?.Success === "1" && | |
| info?.ID | |
| ); | |
| }; | |
| /* | |
| * Read every filter from the current NVIDIA form. | |
| */ | |
| const selectionSnapshot = | |
| () => ({ | |
| revision: | |
| selectionRevision, | |
| productTypeId: | |
| controls.productType.value, | |
| productTypeName: | |
| selectedText( | |
| controls.productType | |
| ), | |
| seriesId: | |
| controls.series.value, | |
| seriesName: | |
| selectedText( | |
| controls.series | |
| ), | |
| productId: | |
| controls.product.value, | |
| productName: | |
| selectedText( | |
| controls.product | |
| ), | |
| osId: | |
| controls.os.value, | |
| osName: | |
| selectedOsName(), | |
| driverType: | |
| supportsWindowsDriverType() && | |
| controls.driverType | |
| ? controls.driverType.value | |
| : null, | |
| driverTypeName: | |
| supportsWindowsDriverType() && | |
| controls.driverType | |
| ? selectedText( | |
| controls.driverType | |
| ) | |
| : "", | |
| channel: | |
| controls.channel.value, | |
| channelName: | |
| selectedText( | |
| controls.channel | |
| ), | |
| languageId: | |
| controls.language.value, | |
| languageName: | |
| selectedText( | |
| controls.language | |
| ), | |
| cudaToolkitId: | |
| controls.productType.value === | |
| "7" && | |
| controls.cuda?.value !== "0" | |
| ? controls.cuda.value | |
| : null | |
| }); | |
| const validateSnapshot = | |
| snapshot => { | |
| if (!snapshot.seriesId) { | |
| throw new Error( | |
| "Select a Product Series first." | |
| ); | |
| } | |
| if (!snapshot.osId) { | |
| throw new Error( | |
| "Select an Operating System first." | |
| ); | |
| } | |
| /* | |
| * Product ID is deliberately optional. Some series operate at series | |
| * level and legitimately do not provide a Product selection. | |
| */ | |
| }; | |
| const makeParams = ( | |
| snapshot, | |
| release, | |
| numberOfResults = null | |
| ) => { | |
| const params = | |
| new URLSearchParams({ | |
| func: | |
| "DriverManualLookup", | |
| psid: | |
| snapshot.seriesId, | |
| osID: | |
| snapshot.osId, | |
| languageID: | |
| snapshot.languageId, | |
| dltype: | |
| "-1", | |
| sort1: | |
| "1", | |
| release: | |
| String(release) | |
| }); | |
| if (snapshot.productId) { | |
| params.set( | |
| "pfid", | |
| snapshot.productId | |
| ); | |
| } | |
| if ( | |
| snapshot.driverType != null | |
| ) { | |
| params.set( | |
| "dch", | |
| snapshot.driverType | |
| ); | |
| } | |
| if ( | |
| snapshot.cudaToolkitId | |
| ) { | |
| params.set( | |
| "cudaToolkitID", | |
| snapshot.cudaToolkitId | |
| ); | |
| } | |
| /* | |
| * Translate NVIDIA's old Recommended/Beta selector to the equivalent | |
| * AjaxDriverService parameters. | |
| */ | |
| switch (snapshot.channel) { | |
| case "1": | |
| params.set( | |
| "isWHQL", | |
| "1" | |
| ); | |
| break; | |
| case "0": | |
| params.set( | |
| "beta", | |
| "1" | |
| ); | |
| break; | |
| case "4": | |
| params.set( | |
| "upCRD", | |
| "1" | |
| ); | |
| break; | |
| case "3": | |
| case "5": | |
| params.set( | |
| "qnf", | |
| "1" | |
| ); | |
| break; | |
| default: | |
| /* | |
| * All: omit the channel filter. | |
| */ | |
| break; | |
| } | |
| if ( | |
| numberOfResults != null | |
| ) { | |
| params.set( | |
| "numberOfResults", | |
| String(numberOfResults) | |
| ); | |
| } | |
| return params; | |
| }; | |
| const fetchRelease = async ( | |
| snapshot, | |
| release, | |
| { | |
| numberOfResults = null, | |
| signal | |
| } = {} | |
| ) => { | |
| const params = makeParams( | |
| snapshot, | |
| release, | |
| numberOfResults | |
| ); | |
| const response = await fetch( | |
| `${DRIVER_API}?${params}`, | |
| { | |
| signal, | |
| cache: "no-store" | |
| } | |
| ); | |
| if (!response.ok) { | |
| throw new Error( | |
| `NVIDIA API returned HTTP ${response.status}.` | |
| ); | |
| } | |
| const data = | |
| await response.json(); | |
| return validDownloadRows(data); | |
| }; | |
| const mapConcurrent = async ( | |
| values, | |
| concurrency, | |
| worker, | |
| onProgress | |
| ) => { | |
| const output = | |
| new Array(values.length); | |
| let next = 0; | |
| let completed = 0; | |
| const runWorker = | |
| async () => { | |
| while (true) { | |
| const index = next++; | |
| if ( | |
| index >= values.length | |
| ) { | |
| return; | |
| } | |
| output[index] = | |
| await worker( | |
| values[index], | |
| index | |
| ); | |
| completed++; | |
| onProgress?.( | |
| completed, | |
| values.length | |
| ); | |
| } | |
| }; | |
| await Promise.all( | |
| Array.from( | |
| { | |
| length: | |
| Math.min( | |
| concurrency, | |
| values.length | |
| ) | |
| }, | |
| runWorker | |
| ) | |
| ); | |
| return output; | |
| }; | |
| const formatSelection = | |
| snapshot => { | |
| const parts = [ | |
| snapshot.productName || | |
| snapshot.seriesName, | |
| snapshot.osName | |
| ]; | |
| if ( | |
| snapshot.driverTypeName | |
| ) { | |
| parts.push( | |
| snapshot.driverTypeName | |
| ); | |
| } | |
| if ( | |
| snapshot.channelName | |
| ) { | |
| parts.push( | |
| snapshot.channelName | |
| ); | |
| } | |
| return parts | |
| .filter(Boolean) | |
| .join(" · "); | |
| }; | |
| const populateBranchOptions = | |
| branches => { | |
| const placeholder = | |
| document.createElement( | |
| "option" | |
| ); | |
| placeholder.value = ""; | |
| placeholder.textContent = | |
| branches.length | |
| ? "Choose a discovered branch" | |
| : "No matching branch found"; | |
| branchOptions.replaceChildren( | |
| placeholder | |
| ); | |
| const sorted = | |
| [...branches].sort( | |
| (a, b) => | |
| b.release - a.release | |
| ); | |
| for ( | |
| const branch of sorted | |
| ) { | |
| const option = | |
| document.createElement( | |
| "option" | |
| ); | |
| option.value = | |
| String(branch.release); | |
| option.textContent = | |
| `R${branch.release} — newest match ` + | |
| `${branch.newest.Version || "found"}`; | |
| branchOptions.append(option); | |
| } | |
| }; | |
| /* | |
| * Test all known branches, then probe above the newest known branch until | |
| * FUTURE_MISS_LIMIT consecutive branch numbers after the newest hit are | |
| * empty. | |
| */ | |
| const scanBranches = async () => { | |
| activeController?.abort(); | |
| const controller = | |
| new AbortController(); | |
| activeController = | |
| controller; | |
| const snapshot = | |
| selectionSnapshot(); | |
| validateSnapshot(snapshot); | |
| scanButton.disabled = true; | |
| listButton.disabled = true; | |
| results.replaceChildren(); | |
| discoveredBranches = []; | |
| const tested = new Set(); | |
| const found = new Map(); | |
| const maxKnown = | |
| Math.max(...KNOWN_RELEASES); | |
| let futureTestedThrough = | |
| maxKnown; | |
| let futureTarget = | |
| maxKnown + | |
| FUTURE_MISS_LIMIT; | |
| let totalCompleted = 0; | |
| const testBatch = | |
| async releases => { | |
| const untested = | |
| releases.filter( | |
| release => | |
| !tested.has(release) | |
| ); | |
| untested.forEach( | |
| release => | |
| tested.add(release) | |
| ); | |
| if (!untested.length) { | |
| return; | |
| } | |
| const batchResults = | |
| await mapConcurrent( | |
| untested, | |
| SCAN_CONCURRENCY, | |
| async release => { | |
| const rows = | |
| await fetchRelease( | |
| snapshot, | |
| release, | |
| { | |
| signal: | |
| controller.signal | |
| } | |
| ); | |
| return { | |
| release, | |
| newest: | |
| rows[0] || null | |
| }; | |
| }, | |
| completed => { | |
| setStatus( | |
| `Scanning ${formatSelection(snapshot)} — ` + | |
| `checked ${totalCompleted + completed} branches...` | |
| ); | |
| } | |
| ); | |
| totalCompleted += | |
| untested.length; | |
| for ( | |
| const result | |
| of batchResults | |
| ) { | |
| if (result.newest) { | |
| found.set( | |
| result.release, | |
| result | |
| ); | |
| } | |
| } | |
| }; | |
| try { | |
| await testBatch( | |
| KNOWN_RELEASES | |
| ); | |
| while ( | |
| futureTestedThrough < | |
| futureTarget | |
| ) { | |
| const end = Math.min( | |
| futureTarget, | |
| futureTestedThrough + | |
| SCAN_CONCURRENCY * 3 | |
| ); | |
| const releases = | |
| Array.from( | |
| { | |
| length: | |
| end - | |
| futureTestedThrough | |
| }, | |
| (_, index) => | |
| futureTestedThrough + | |
| index + | |
| 1 | |
| ); | |
| await testBatch(releases); | |
| futureTestedThrough = | |
| end; | |
| const newerFound = | |
| [...found.keys()] | |
| .filter( | |
| release => | |
| release > maxKnown | |
| ); | |
| const newestFutureRelease = | |
| Math.max( | |
| maxKnown, | |
| ...newerFound | |
| ); | |
| futureTarget = | |
| Math.max( | |
| futureTarget, | |
| newestFutureRelease + | |
| FUTURE_MISS_LIMIT | |
| ); | |
| } | |
| if ( | |
| snapshot.revision !== | |
| selectionRevision | |
| ) { | |
| return; | |
| } | |
| discoveredBranches = | |
| [...found.values()] | |
| .sort( | |
| (a, b) => | |
| b.release - | |
| a.release | |
| ); | |
| populateBranchOptions( | |
| discoveredBranches | |
| ); | |
| if ( | |
| discoveredBranches.length | |
| ) { | |
| const newestRelease = | |
| String( | |
| discoveredBranches[0] | |
| .release | |
| ); | |
| branchOptions.value = | |
| newestRelease; | |
| branchInput.value = | |
| newestRelease; | |
| setStatus( | |
| `Found ${discoveredBranches.length} matching release branches ` + | |
| `for ${formatSelection(snapshot)}. ` + | |
| `The newest matching driver from each branch was used for this scan.` | |
| ); | |
| } else { | |
| setStatus( | |
| `No matching branch was found for ${formatSelection(snapshot)}. ` + | |
| `You can still enter a release number directly.` | |
| ); | |
| } | |
| } catch (error) { | |
| controller.abort(); | |
| if ( | |
| error.name !== "AbortError" | |
| ) { | |
| console.error(error); | |
| setStatus( | |
| `Branch scan failed: ${error.message}` | |
| ); | |
| } | |
| } finally { | |
| if ( | |
| activeController === | |
| controller | |
| ) { | |
| activeController = null; | |
| } | |
| scanButton.disabled = false; | |
| listButton.disabled = false; | |
| } | |
| }; | |
| /* | |
| * Retrieve all matching drivers in a branch. If NVIDIA fills the requested | |
| * result count exactly, retry with a larger count. | |
| */ | |
| const fetchEveryDriverInRelease = | |
| async ( | |
| snapshot, | |
| release, | |
| signal | |
| ) => { | |
| let limit = | |
| INITIAL_FULL_RESULT_LIMIT; | |
| let previousCount = -1; | |
| let rows = []; | |
| while (true) { | |
| rows = await fetchRelease( | |
| snapshot, | |
| release, | |
| { | |
| numberOfResults: | |
| limit, | |
| signal | |
| } | |
| ); | |
| if ( | |
| rows.length < limit || | |
| rows.length === | |
| previousCount || | |
| limit >= | |
| MAX_FULL_RESULT_LIMIT | |
| ) { | |
| return { | |
| rows, | |
| possiblyTruncated: | |
| rows.length >= limit && | |
| limit >= | |
| MAX_FULL_RESULT_LIMIT | |
| }; | |
| } | |
| previousCount = | |
| rows.length; | |
| limit = Math.min( | |
| limit * 2, | |
| MAX_FULL_RESULT_LIMIT | |
| ); | |
| } | |
| }; | |
| const safeLink = value => { | |
| const decoded = | |
| decode(value); | |
| try { | |
| const url = new URL( | |
| decoded, | |
| location.origin | |
| ); | |
| return ( | |
| url.protocol === "https:" | |
| ? url.href | |
| : null | |
| ); | |
| } catch { | |
| return null; | |
| } | |
| }; | |
| const renderRows = rows => { | |
| results.replaceChildren(); | |
| if (!rows.length) { | |
| return; | |
| } | |
| const table = | |
| document.createElement( | |
| "table" | |
| ); | |
| const thead = | |
| document.createElement( | |
| "thead" | |
| ); | |
| const headerRow = | |
| document.createElement( | |
| "tr" | |
| ); | |
| const headings = [ | |
| "Version", | |
| "Release date", | |
| "Driver", | |
| "Certification", | |
| "Package", | |
| "Links" | |
| ]; | |
| for ( | |
| const label | |
| of headings | |
| ) { | |
| const th = | |
| document.createElement( | |
| "th" | |
| ); | |
| th.textContent = label; | |
| headerRow.append(th); | |
| } | |
| thead.append(headerRow); | |
| table.append(thead); | |
| const tbody = | |
| document.createElement( | |
| "tbody" | |
| ); | |
| for (const row of rows) { | |
| const tr = | |
| document.createElement( | |
| "tr" | |
| ); | |
| const certification = [ | |
| row.IsWHQL === "1" | |
| ? "WHQL" | |
| : "", | |
| row.IsBeta === "1" | |
| ? "Beta" | |
| : "" | |
| ] | |
| .filter(Boolean) | |
| .join(" · ") || "—"; | |
| const packageType = | |
| row.IsDC === "1" | |
| ? "DCH" | |
| : /^Windows\b/i.test( | |
| decode(row.OSName) | |
| ) | |
| ? "Standard" | |
| : "—"; | |
| const values = [ | |
| row.Version || "", | |
| row.ReleaseDateTime || | |
| "", | |
| decode( | |
| row.NameLocalized || | |
| row.Name | |
| ), | |
| certification, | |
| packageType | |
| ]; | |
| values.forEach( | |
| (value, index) => { | |
| const td = | |
| document.createElement( | |
| "td" | |
| ); | |
| td.textContent = value; | |
| if (index === 2) { | |
| td.className = | |
| "nv-name"; | |
| } | |
| tr.append(td); | |
| } | |
| ); | |
| const linksCell = | |
| document.createElement( | |
| "td" | |
| ); | |
| const links = | |
| document.createElement( | |
| "div" | |
| ); | |
| links.className = | |
| "nv-links"; | |
| const linkDefinitions = [ | |
| [ | |
| "Details", | |
| row.DetailsURL | |
| ], | |
| [ | |
| "Download", | |
| row.DownloadURL | |
| ] | |
| ]; | |
| for ( | |
| const [label, value] | |
| of linkDefinitions | |
| ) { | |
| const href = | |
| safeLink(value); | |
| if (!href) { | |
| continue; | |
| } | |
| const anchor = | |
| document.createElement( | |
| "a" | |
| ); | |
| anchor.href = href; | |
| anchor.target = "_blank"; | |
| anchor.rel = | |
| "noopener noreferrer"; | |
| anchor.textContent = label; | |
| links.append(anchor); | |
| } | |
| linksCell.append(links); | |
| tr.append(linksCell); | |
| tbody.append(tr); | |
| } | |
| table.append(tbody); | |
| results.append(table); | |
| }; | |
| const listBranch = async () => { | |
| const release = | |
| Number.parseInt( | |
| branchInput.value, | |
| 10 | |
| ); | |
| if ( | |
| !Number.isInteger(release) || | |
| release < 1 | |
| ) { | |
| setStatus( | |
| "Enter a valid numeric release branch, such as 470 or 580." | |
| ); | |
| return; | |
| } | |
| activeController?.abort(); | |
| const controller = | |
| new AbortController(); | |
| activeController = | |
| controller; | |
| const snapshot = | |
| selectionSnapshot(); | |
| validateSnapshot(snapshot); | |
| scanButton.disabled = true; | |
| listButton.disabled = true; | |
| results.replaceChildren(); | |
| setStatus( | |
| `Loading every matching R${release} driver for ` + | |
| `${formatSelection(snapshot)}...` | |
| ); | |
| try { | |
| const { | |
| rows, | |
| possiblyTruncated | |
| } = | |
| await fetchEveryDriverInRelease( | |
| snapshot, | |
| release, | |
| controller.signal | |
| ); | |
| if ( | |
| snapshot.revision !== | |
| selectionRevision | |
| ) { | |
| return; | |
| } | |
| /* | |
| * De-duplicate by download ID. Do not de-duplicate by version because | |
| * Game Ready and Studio can legitimately share a version number. | |
| */ | |
| const uniqueRows = [ | |
| ...new Map( | |
| rows.map( | |
| row => [ | |
| row.ID, | |
| row | |
| ] | |
| ) | |
| ).values() | |
| ]; | |
| renderRows(uniqueRows); | |
| if (uniqueRows.length) { | |
| const plural = | |
| uniqueRows.length === 1 | |
| ? "" | |
| : "s"; | |
| const truncationWarning = | |
| possiblyTruncated | |
| ? ( | |
| ". The NVIDIA service still filled its largest requested " + | |
| "result set, so additional records may exist" | |
| ) | |
| : ""; | |
| setStatus( | |
| `R${release}: ${uniqueRows.length} matching driver${plural} ` + | |
| `for ${formatSelection(snapshot)}${truncationWarning}.` | |
| ); | |
| console.table( | |
| uniqueRows.map( | |
| row => ({ | |
| branch: | |
| `R${row.Release || release}`, | |
| version: | |
| row.Version, | |
| date: | |
| row.ReleaseDateTime, | |
| driver: | |
| decode( | |
| row.NameLocalized || | |
| row.Name | |
| ), | |
| whql: | |
| row.IsWHQL === "1", | |
| dch: | |
| row.IsDC === "1", | |
| details: | |
| decode( | |
| row.DetailsURL | |
| ), | |
| download: | |
| decode( | |
| row.DownloadURL | |
| ) | |
| }) | |
| ) | |
| ); | |
| } else { | |
| setStatus( | |
| `No matching R${release} drivers were returned for ` + | |
| `${formatSelection(snapshot)}.` | |
| ); | |
| } | |
| } catch (error) { | |
| if ( | |
| error.name !== "AbortError" | |
| ) { | |
| console.error(error); | |
| setStatus( | |
| `Driver lookup failed: ${error.message}` | |
| ); | |
| } | |
| } finally { | |
| if ( | |
| activeController === | |
| controller | |
| ) { | |
| activeController = null; | |
| } | |
| scanButton.disabled = false; | |
| listButton.disabled = false; | |
| } | |
| }; | |
| /* | |
| * Changing any NVIDIA filter invalidates the discovered branch list. | |
| */ | |
| const invalidateSelection = | |
| () => { | |
| selectionRevision++; | |
| activeController?.abort(); | |
| activeController = null; | |
| discoveredBranches = []; | |
| const placeholder = | |
| document.createElement( | |
| "option" | |
| ); | |
| placeholder.value = ""; | |
| placeholder.textContent = | |
| "Scan branches again"; | |
| branchOptions.replaceChildren( | |
| placeholder | |
| ); | |
| branchInput.value = ""; | |
| results.replaceChildren(); | |
| setTimeout( | |
| syncDriverTypeVisibility, | |
| 0 | |
| ); | |
| setStatus( | |
| "Selection changed. Scan branches again or enter a branch directly." | |
| ); | |
| }; | |
| const listen = ( | |
| target, | |
| event, | |
| handler | |
| ) => { | |
| if (!target) { | |
| return; | |
| } | |
| target.addEventListener( | |
| event, | |
| handler | |
| ); | |
| listeners.push( | |
| () => | |
| target.removeEventListener( | |
| event, | |
| handler | |
| ) | |
| ); | |
| }; | |
| const watchedControls = [ | |
| controls.productType, | |
| controls.series, | |
| controls.product, | |
| controls.os, | |
| controls.driverType, | |
| controls.channel, | |
| controls.language, | |
| controls.cuda | |
| ]; | |
| for ( | |
| const control | |
| of watchedControls | |
| ) { | |
| listen( | |
| control, | |
| "change", | |
| invalidateSelection | |
| ); | |
| } | |
| listen( | |
| scanButton, | |
| "click", | |
| () => { | |
| scanBranches().catch( | |
| error => { | |
| console.error(error); | |
| setStatus(error.message); | |
| scanButton.disabled = | |
| false; | |
| listButton.disabled = | |
| false; | |
| } | |
| ); | |
| } | |
| ); | |
| listen( | |
| listButton, | |
| "click", | |
| () => { | |
| listBranch().catch( | |
| error => { | |
| console.error(error); | |
| setStatus(error.message); | |
| scanButton.disabled = | |
| false; | |
| listButton.disabled = | |
| false; | |
| } | |
| ); | |
| } | |
| ); | |
| /* | |
| * Choosing an available branch copies it into the editable field used by | |
| * List every driver in branch. | |
| */ | |
| listen( | |
| branchOptions, | |
| "change", | |
| () => { | |
| if (branchOptions.value) { | |
| branchInput.value = | |
| branchOptions.value; | |
| } | |
| } | |
| ); | |
| listen( | |
| branchInput, | |
| "keydown", | |
| event => { | |
| if (event.key === "Enter") { | |
| listButton.click(); | |
| } | |
| } | |
| ); | |
| /* | |
| * Expose a small console API. | |
| */ | |
| window.nvDriverArchive = { | |
| version: | |
| SCRIPT_VERSION, | |
| knownReleases: | |
| [...KNOWN_RELEASES], | |
| maxHistoricalGap: | |
| MAX_HISTORICAL_GAP, | |
| futureMissLimit: | |
| FUTURE_MISS_LIMIT, | |
| scanBranches, | |
| listBranch, | |
| get discoveredBranches() { | |
| return [ | |
| ...discoveredBranches | |
| ]; | |
| }, | |
| destroy() { | |
| activeController?.abort(); | |
| listeners | |
| .splice(0) | |
| .forEach( | |
| removeListener => | |
| removeListener() | |
| ); | |
| panel.remove(); | |
| style.remove(); | |
| if ( | |
| window.nvDriverArchive === | |
| this | |
| ) { | |
| delete window | |
| .nvDriverArchive; | |
| } | |
| } | |
| }; | |
| syncDriverTypeVisibility(); | |
| console.info( | |
| `NVIDIA driver-search fix ${SCRIPT_VERSION} installed. ` + | |
| `Historical max branch gap: ${MAX_HISTORICAL_GAP}; ` + | |
| `future scan stop: ${FUTURE_MISS_LIMIT} misses.` | |
| ); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment