您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds the ability to handle hash validator for SHA1 files based on Script by Sak32009
// ==UserScript== // @name SHA1 Calculator // @namespace SWScripts // @version v1.1 // @description Adds the ability to handle hash validator for SHA1 files based on Script by Sak32009 // @license MIT // @author BN_LOS // @match https://steamdb.info/depot/*/?show_hashes // @grant none // ==/UserScript== (function() { 'use strict'; const addStyle = (css) => { const style = document.createElement('style'); style.textContent = css; document.head.appendChild(style); }; const showToast = (message, type = 'info') => { const toast = document.createElement('div'); toast.classList.add('toast', `alert-${type}`); toast.textContent = message; document.body.appendChild(toast); setTimeout(() => toast.remove(), 3000); }; addStyle(` .toast { position: fixed; top: 1rem; right: 1rem; padding: 1rem; border-radius: 0.375rem; opacity: 0; transition: opacity 0.3s; z-index: 10000; } .toast.alert-info { background-color: #e2f3f5; color: #0c5460; } .toast.alert-error { background-color: #f8d7da; color: #721c24; } .status-summary { display: flex; gap: 1rem; margin-top: 1rem; font-size: 1rem; color: #555; } #notFoundFilesContainer { margin-top: 1rem; } `); const fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.id = 'fileInput'; fileInput.accept = '.sha1'; fileInput.style.display = 'none'; document.body.appendChild(fileInput); const loadButton = document.createElement('button'); loadButton.className = 'btn btn-outline mb-4'; loadButton.textContent = 'Load .SHA1'; const dtSearchDiv = document.querySelector('.dt-search'); dtSearchDiv?.insertAdjacentElement('beforebegin', loadButton); const statusSummary = document.createElement('div'); statusSummary.className = 'status-summary'; statusSummary.innerHTML = '<span>Valid: <span id="validCount">0</span></span><span>Invalid: <span id="invalidCount">0</span></span><span>Not Found: <span id="notFoundCount">0</span></span><span>Total: <span id="totalCount">0</span></span>'; const dataTableDisplayDiv = document.querySelector('.dataTable_display'); dataTableDisplayDiv?.insertAdjacentElement('afterend', statusSummary); let sha1Data = []; const table = document.querySelector('#DataTables_Table_0'); const th = document.createElement('th'); th.textContent = 'Status'; table.querySelector('thead tr')?.appendChild(th); const dataTableLengthSelect = document.getElementById('dt-length-0'); if (dataTableLengthSelect) { dataTableLengthSelect.value = '-1'; dataTableLengthSelect.dispatchEvent(new Event('change')); } loadButton.addEventListener('click', () => fileInput.click()); fileInput.addEventListener('change', async (event) => { const file = event.target.files[0]; if (!file || !file.name.endsWith('.sha1')) { showToast('Invalid file. Please select a .sha1 file.', 'error'); return; } try { const content = await file.text(); sha1Data = []; clearTableStatus(); clearNotInDepotTable(); updateStatusSummary(0, 0, 0); updateDownloadButton([]); sha1Data = parseSHA1File(content); updateTable(); } catch (error) { showToast('Error reading file.', 'error'); console.error(error); } }); const parseSHA1File = (content) => { return content.split('\n') .map(line => line.trim()) .filter(line => line) .map(line => { const [hash, filename] = line.split(' *'); return { hash, filename: filename.replace(/[\\\/]/g, '/').trim() }; }); }; const clearTableStatus = () => { const rows = table.querySelectorAll('tbody tr'); rows.forEach(row => { if (row.cells.length > 3) { row.deleteCell(3); } }); }; const clearNotInDepotTable = () => { const notInDepotTable = document.getElementById('DataTables_Table_2_wrapper'); if (notInDepotTable) { notInDepotTable.remove(); } }; const createNotInDepotTable = (notFoundInPageFiles) => { const wrapper = document.getElementById('DataTables_Table_0_wrapper'); if (!wrapper) return; const existingTable2 = document.getElementById('DataTables_Table_2_wrapper'); if (existingTable2) existingTable2.remove(); if (notFoundInPageFiles.length === 0) return; const table2Wrapper = document.createElement('div'); table2Wrapper.id = 'DataTables_Table_2_wrapper'; table2Wrapper.innerHTML = `<h3>Not in Depot</h3>`; const table2 = document.createElement('table'); table2.className = 'table table-bordered table-fixed dataTable mt-4'; table2.innerHTML = ` <thead><tr><th>Filename</th><th>Hashcode</th></tr></thead> <tbody>${notFoundInPageFiles.map(file => `<tr><td>${file.filename}</td><td>${file.hash}</td></tr>`).join('')}</tbody> `; table2Wrapper.appendChild(table2); wrapper.insertAdjacentElement('afterend', table2Wrapper); }; const updateTable = () => { clearTableStatus(); let validCount = 0; let invalidCount = 0; let notFoundCount = 0; const notFoundFiles = []; const rows = table.querySelectorAll('tbody tr'); rows.forEach(row => { const filenameCell = row.cells[0]; const hashCell = row.cells[1]; const filename = filenameCell.textContent.trim().replace(/[\\\/]/g, '/'); const pageHash = hashCell.textContent.replace('***', '').trim(); const statusCell = document.createElement('td'); if (!pageHash && filename) { statusCell.textContent = 'Folder'; statusCell.className = 'text-blue-500'; row.appendChild(statusCell); return; } const sha1Entry = sha1Data.find(entry => entry.filename === filename); if (sha1Entry) { const startHash = sha1Entry.hash.slice(0, 10); const endHash = sha1Entry.hash.slice(-10); const pageStartHash = pageHash.slice(0, 10); const pageEndHash = pageHash.slice(-10); if (startHash === pageStartHash && endHash === pageEndHash) { statusCell.textContent = 'Matched'; statusCell.className = 'text-green-500'; validCount++; } else { statusCell.textContent = 'Unmatched'; statusCell.className = 'text-red-500'; invalidCount++; } } else { statusCell.textContent = 'Not Found'; statusCell.className = 'text-gray-500'; notFoundFiles.push(filename); notFoundCount++; } row.appendChild(statusCell); }); updateStatusSummary(validCount, invalidCount, notFoundCount); updateDownloadButton(notFoundFiles); const notFoundInPageFiles = sha1Data.filter(sha1File => { return !Array.from(table.querySelectorAll('tbody tr td:first-child')).some(cell => { return cell.textContent.trim().replace(/[\\\/]/g, '/') === sha1File.filename; }); }); createNotInDepotTable(notFoundInPageFiles); }; const updateStatusSummary = (valid, invalid, notFound) => { document.getElementById('validCount').textContent = valid; document.getElementById('invalidCount').textContent = invalid; document.getElementById('notFoundCount').textContent = notFound; document.getElementById('totalCount').textContent = valid + invalid + notFound; }; const updateDownloadButton = (notFoundFiles) => { const buttonContainer = document.getElementById('notFoundFilesContainer'); if (!notFoundFiles.length) { if (buttonContainer) { buttonContainer.remove(); } } else { if (!buttonContainer) { const container = document.createElement('div'); container.id = 'notFoundFilesContainer'; container.innerHTML = `<button class="btn btn-outline" id="downloadButton">Download Missing Files</button>`; document.body.appendChild(container); document.getElementById('downloadButton').addEventListener('click', downloadMissingFiles); } } }; const downloadMissingFiles = () => { const blob = new Blob([notFoundFiles.join('\n')], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'missing_files.sha1'; a.click(); URL.revokeObjectURL(url); }; })();