Skip to content

Commit a40a9b6

Browse files
authored
Add files via upload
1 parent 5a3b617 commit a40a9b6

1 file changed

Lines changed: 371 additions & 0 deletions

File tree

index.html

Lines changed: 371 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,371 @@
1+
<!DOCTYPE html>
2+
<html lang="zh-CN">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>二进制文件损坏工具</title>
7+
<script src="https://cdn.tailwindcss.com"></script>
8+
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
9+
<script>
10+
tailwind.config = {
11+
theme: {
12+
extend: {
13+
colors: {
14+
primary: '#3B82F6',
15+
secondary: '#10B981',
16+
danger: '#EF4444',
17+
dark: '#1F2937',
18+
},
19+
fontFamily: {
20+
sans: ['Inter', 'system-ui', 'sans-serif'],
21+
},
22+
}
23+
}
24+
}
25+
</script>
26+
<style type="text/tailwindcss">
27+
@layer utilities {
28+
.content-auto {
29+
content-visibility: auto;
30+
}
31+
.file-drop-area {
32+
@apply border-2 border-dashed border-gray-300 rounded-lg p-8 text-center transition-all duration-300;
33+
}
34+
.file-drop-area.active {
35+
@apply border-primary bg-blue-50;
36+
}
37+
.btn-primary {
38+
@apply bg-primary hover:bg-primary/90 text-white font-medium py-2 px-4 rounded-lg transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-primary/50;
39+
}
40+
.btn-secondary {
41+
@apply bg-secondary hover:bg-secondary/90 text-white font-medium py-2 px-4 rounded-lg transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-secondary/50;
42+
}
43+
.input-field {
44+
@apply w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary transition-all duration-200;
45+
}
46+
}
47+
</style>
48+
</head>
49+
<body class="bg-gray-50 min-h-screen font-sans">
50+
<div class="container mx-auto px-4 py-8 max-w-4xl">
51+
<header class="mb-8 text-center">
52+
<h1 class="text-[clamp(1.8rem,4vw,2.5rem)] font-bold text-dark mb-2">
53+
<<i class="fa fa-code-fork text-primary mr-2"></</i>二进制文件损坏工具
54+
</h1>
55+
<p class="text-gray-600 text-lg">在不修改文件头的情况下,每隔指定字节损坏2字节数据</p>
56+
</header>
57+
58+
<main class="bg-white rounded-xl shadow-md p-6 md:p-8 mb-8">
59+
<!-- 文件上传区域 -->
60+
<div id="fileDropArea" class="file-drop-area mb-8">
61+
<<i class="fa fa-cloud-upload text-5xl text-gray-400 mb-4"></</i>
62+
<p class="text-gray-600 mb-2">拖放文件到这里,或</p>
63+
<label class="btn-primary inline-block cursor-pointer">
64+
<<i class="fa fa-file-o mr-1"></</i> 选择文件
65+
<input type="file" id="fileInput" class="hidden" accept="*">
66+
</label>
67+
<p id="fileName" class="mt-4 text-gray-500 text-sm hidden"></p>
68+
</div>
69+
70+
<!-- 参数设置 -->
71+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
72+
<div>
73+
<label for="headerLength" class="block text-gray-700 font-medium mb-2">
74+
文件头长度 (字节)
75+
</label>
76+
<div class="flex items-center">
77+
<input
78+
type="number"
79+
id="headerLength"
80+
class="input-field"
81+
value="512"
82+
min="0"
83+
placeholder="不修改的文件头长度"
84+
>
85+
<span class="ml-2 text-gray-500">字节</span>
86+
</div>
87+
<p class="text-gray-500 text-sm mt-1">文件开头的这些字节将保持不变</p>
88+
</div>
89+
<div>
90+
<label for="damageInterval" class="block text-gray-700 font-medium mb-2">
91+
损坏间隔 (字节)
92+
</label>
93+
<div class="flex items-center">
94+
<input
95+
type="number"
96+
id="damageInterval"
97+
class="input-field"
98+
value="1024"
99+
min="2"
100+
placeholder="每隔多少字节损坏一次"
101+
>
102+
<span class="ml-2 text-gray-500">字节</span>
103+
</div>
104+
<p class="text-gray-500 text-sm mt-1">每隔指定字节,损坏接下来的2字节</p>
105+
</div>
106+
</div>
107+
108+
<!-- 处理按钮 -->
109+
<div class="text-center mb-8">
110+
<button id="processBtn" class="btn-primary disabled:opacity-50 disabled:cursor-not-allowed" disabled>
111+
<<i class="fa fa-wrench mr-2"></</i>开始损坏文件
112+
</button>
113+
</div>
114+
115+
<!-- 进度和结果 -->
116+
<div id="progressArea" class="hidden mb-8">
117+
<div class="bg-gray-200 rounded-full h-2.5 mb-2">
118+
<div id="progressBar" class="bg-primary h-2.5 rounded-full" style="width: 0%"></div>
119+
</div>
120+
<p id="progressText" class="text-gray-600 text-center">准备中...</p>
121+
</div>
122+
123+
<div id="resultArea" class="hidden mb-8 text-center">
124+
<div class="p-4 bg-green-50 border border-green-200 rounded-lg mb-4">
125+
<<i class="fa fa-check-circle text-secondary text-2xl mb-2"></</i>
126+
<h3 class="text-lg font-medium text-gray-800">文件处理完成!</h3>
127+
<p id="resultInfo" class="text-gray-600 mt-1"></p>
128+
</div>
129+
<button id="downloadBtn" class="btn-secondary">
130+
<<i class="fa fa-download mr-2"></</i>下载损坏后的文件
131+
</button>
132+
</div>
133+
134+
<!-- 错误提示 -->
135+
<div id="errorArea" class="hidden mb-8">
136+
<div class="p-4 bg-red-50 border border-red-200 rounded-lg">
137+
<<i class="fa fa-exclamation-circle text-danger text-xl mb-2"></</i>
138+
<p id="errorMessage" class="text-gray-700"></p>
139+
</div>
140+
</div>
141+
</main>
142+
143+
<footer class="text-center text-gray-500 text-sm">
144+
<p>二进制文件损坏工具 &copy; 2023 | 在浏览器中本地处理,不会上传您的文件</p>
145+
</footer>
146+
</div>
147+
148+
<script>
149+
// 获取DOM元素
150+
const fileDropArea = document.getElementById('fileDropArea');
151+
const fileInput = document.getElementById('fileInput');
152+
const fileName = document.getElementById('fileName');
153+
const headerLengthInput = document.getElementById('headerLength');
154+
const damageIntervalInput = document.getElementById('damageInterval');
155+
const processBtn = document.getElementById('processBtn');
156+
const progressArea = document.getElementById('progressArea');
157+
const progressBar = document.getElementById('progressBar');
158+
const progressText = document.getElementById('progressText');
159+
const resultArea = document.getElementById('resultArea');
160+
const resultInfo = document.getElementById('resultInfo');
161+
const downloadBtn = document.getElementById('downloadBtn');
162+
const errorArea = document.getElementById('errorArea');
163+
const errorMessage = document.getElementById('errorMessage');
164+
165+
// 存储选中的文件
166+
let selectedFile = null;
167+
let processedBlob = null;
168+
let processedFileName = '';
169+
170+
// 监听文件拖放事件
171+
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
172+
fileDropArea.addEventListener(eventName, preventDefaults, false);
173+
});
174+
175+
function preventDefaults(e) {
176+
e.preventDefault();
177+
e.stopPropagation();
178+
}
179+
180+
['dragenter', 'dragover'].forEach(eventName => {
181+
fileDropArea.addEventListener(eventName, highlight, false);
182+
});
183+
184+
['dragleave', 'drop'].forEach(eventName => {
185+
fileDropArea.addEventListener(eventName, unhighlight, false);
186+
});
187+
188+
function highlight() {
189+
fileDropArea.classList.add('active');
190+
}
191+
192+
function unhighlight() {
193+
fileDropArea.classList.remove('active');
194+
}
195+
196+
// 处理文件拖放
197+
fileDropArea.addEventListener('drop', handleDrop, false);
198+
199+
function handleDrop(e) {
200+
const dt = e.dataTransfer;
201+
const file = dt.files[0];
202+
203+
if (file) {
204+
handleFile(file);
205+
}
206+
}
207+
208+
// 处理文件选择
209+
fileInput.addEventListener('change', function() {
210+
if (this.files.length > 0) {
211+
handleFile(this.files[0]);
212+
}
213+
});
214+
215+
// 点击上传区域触发文件选择
216+
fileDropArea.addEventListener('click', function() {
217+
fileInput.click();
218+
});
219+
220+
// 处理选中的文件
221+
function handleFile(file) {
222+
selectedFile = file;
223+
fileName.textContent = `已选择: ${file.name} (${formatFileSize(file.size)})`;
224+
fileName.classList.remove('hidden');
225+
processBtn.disabled = false;
226+
227+
// 重置状态
228+
resultArea.classList.add('hidden');
229+
errorArea.classList.add('hidden');
230+
}
231+
232+
// 格式化文件大小
233+
function formatFileSize(bytes) {
234+
if (bytes === 0) return '0 Bytes';
235+
const k = 1024;
236+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
237+
const i = Math.floor(Math.log(bytes) / Math.log(k));
238+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
239+
}
240+
241+
// 处理文件按钮点击
242+
processBtn.addEventListener('click', processFile);
243+
244+
// 处理文件
245+
function processFile() {
246+
if (!selectedFile) {
247+
showError('请先选择一个文件');
248+
return;
249+
}
250+
251+
// 获取参数
252+
const headerLength = parseInt(headerLengthInput.value, 10);
253+
const damageInterval = parseInt(damageIntervalInput.value, 10);
254+
255+
// 验证参数
256+
if (isNaN(headerLength) || headerLength < 0) {
257+
showError('文件头长度必须是大于等于0的数字');
258+
return;
259+
}
260+
261+
if (isNaN(damageInterval) || damageInterval < 2) {
262+
showError('损坏间隔必须是大于等于2的数字');
263+
return;
264+
}
265+
266+
// 重置状态
267+
resultArea.classList.add('hidden');
268+
errorArea.classList.add('hidden');
269+
progressArea.classList.remove('hidden');
270+
progressBar.style.width = '0%';
271+
progressText.textContent = '正在读取文件...';
272+
273+
const reader = new FileReader();
274+
275+
reader.onload = function(e) {
276+
try {
277+
const arrayBuffer = e.target.result;
278+
const byteArray = new Uint8Array(arrayBuffer);
279+
const fileSize = byteArray.length;
280+
281+
// 更新进度
282+
updateProgress(20, '正在处理文件...');
283+
284+
// 确保文件头长度不超过文件大小
285+
const actualHeaderLength = Math.min(headerLength, fileSize);
286+
287+
// 计算需要损坏的位置数量
288+
const dataSize = fileSize - actualHeaderLength;
289+
if (dataSize <= 0) {
290+
showError('文件头长度大于或等于文件大小,没有可损坏的数据');
291+
return;
292+
}
293+
294+
// 损坏数据:从文件头之后开始,每隔damageInterval字节,损坏2字节
295+
let modifiedCount = 0;
296+
297+
for (let i = actualHeaderLength; i < fileSize; i += damageInterval) {
298+
// 更新进度
299+
const progress = 20 + Math.floor((i / fileSize) * 70);
300+
updateProgress(progress, `正在损坏数据... (${modifiedCount}处)`);
301+
302+
// 损坏接下来的2字节,确保不超出文件范围
303+
for (let j = 0; j < 2 && i + j < fileSize; j++) {
304+
// 生成随机字节值 (0-255)
305+
byteArray[i + j] = Math.floor(Math.random() * 256);
306+
modifiedCount++;
307+
}
308+
}
309+
310+
// 完成处理
311+
updateProgress(100, '处理完成!');
312+
313+
// 创建处理后的文件
314+
processedBlob = new Blob([byteArray], { type: selectedFile.type });
315+
const nameParts = selectedFile.name.split('.');
316+
if (nameParts.length > 1) {
317+
const ext = nameParts.pop();
318+
processedFileName = `${nameParts.join('.')}_damaged.${ext}`;
319+
} else {
320+
processedFileName = `${selectedFile.name}_damaged`;
321+
}
322+
323+
// 显示结果
324+
resultInfo.textContent = `已在文件中损坏 ${modifiedCount/2} 处,共 ${modifiedCount} 字节`;
325+
setTimeout(() => {
326+
progressArea.classList.add('hidden');
327+
resultArea.classList.remove('hidden');
328+
}, 500);
329+
330+
} catch (error) {
331+
showError(`处理文件时出错: ${error.message}`);
332+
}
333+
};
334+
335+
reader.onerror = function() {
336+
showError('读取文件时出错');
337+
};
338+
339+
// 读取文件为ArrayBuffer
340+
reader.readAsArrayBuffer(selectedFile);
341+
}
342+
343+
// 更新进度
344+
function updateProgress(percent, text) {
345+
progressBar.style.width = `${percent}%`;
346+
progressText.textContent = text;
347+
}
348+
349+
// 显示错误
350+
function showError(message) {
351+
progressArea.classList.add('hidden');
352+
errorMessage.textContent = message;
353+
errorArea.classList.remove('hidden');
354+
}
355+
356+
// 下载处理后的文件
357+
downloadBtn.addEventListener('click', function() {
358+
if (processedBlob && processedFileName) {
359+
const url = URL.createObjectURL(processedBlob);
360+
const a = document.createElement('a');
361+
a.href = url;
362+
a.download = processedFileName;
363+
document.body.appendChild(a);
364+
a.click();
365+
document.body.removeChild(a);
366+
URL.revokeObjectURL(url);
367+
}
368+
});
369+
</script>
370+
</body>
371+
</html>

0 commit comments

Comments
 (0)