实测证明 OpenAI 能有效解析和反混淆代码,这是程序员处理遗留代码时的实用工具技巧。
在网上寻找灵感时,我偶然发现了一个看起来很有意思的组件。
我觉得那个运行着 ASCII art 的区块很酷,但一时没弄明白它是怎么实现的,于是便开始查看源代码。
我找到了看起来可能负责实现这一效果的代码,但它已经被 minify 了。
const { floor: ra, abs: KE, min: QE } = Math,
O5 = ["reactive.network REACTIVE.NETWORK", "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/|()1{}[]?-_+~<>i!lI;:,^`'. .:â–‘â–’â–“â–ˆ"],
G7 = Date.now() % 3 ? O5[1] : O5[0],
V5 = G7.length,
JE = { fps: 60 };
function eT(e, t, n, r) {
const i = t.time * 8e-5,
s = QE(t.cols, t.rows),
o = t.metrics.aspect * 0.2,
l = { x: ((4 * (e.x - t.cols / 6.25)) / s) * o, y: (5 * (e.y - t.rows / 4)) / s },
u = ra(KE(YE(l) - i) * V5 + (ra(e.x / 1) % 2) * 2) % V5;
return G7[u];
}
const tT = () => {
const e = j.useRef(null),
[t, n] = j.useState({ height: null, width: null });
return (
j.useEffect(() => {
function r() {
n({ height: window.innerHeight, width: window.innerWidth });
}
if (typeof window < "u") return n({ height: window.innerHeight, width: window.innerWidth }), window.addEventListener("resize", r), () => window.removeEventListener("resize", r);
}, []),
j.useEffect(() => {
const r = e.current;
if (!r) return;
const i = 12,
s = ra(t.width / i) * 1.6,
o = ra(t.height / i),
l = { aspect: s / o },
u = setInterval(() => {
let c = "";
for (let d = 0; d < o; d++) {
for (let f = 0; f < s; f++) c += eT({ x: f, y: d }, { cols: s, rows: o, metrics: l, time: Date.now() });
c += `
`;
}
r.textContent = c;
}, 1e3 / JE.fps);
return () => clearInterval(u);
}, [t]),
a.jsx("div", { style: { position: "absolute", top: 0, left: 0, width: "100%", height: "100%" }, children: a.jsx("div", { ref: e, style: { width: "100%", height: "100%", whiteSpace: "pre", overflow: "hidden" } }) })
);
};
function nT(e) {
return Math.cos(e.x * e.x - e.y * e.y);
}
const { floor: ia, abs: rT, min: iT } = Math,
D5 = ["reactive.network REACTIVE.NETWORK", "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/|()1{}[]?-_+~<>i!lI;:,^`'. .:â–‘â–’â–“â–ˆ"],
X7 = Date.now() % 3 ? D5[1] : D5[0],
F5 = X7.length,
sT = { fps: 60 };
function oT(e, t, n, r) {
const i = t.time * 8e-5,
s = iT(t.cols, t.rows),
o = t.metrics.aspect * 0.2,
l = { x: ((4 * (e.x - t.cols / 6.25)) / s) * o, y: (5 * (e.y - t.rows / 4)) / s },
u = ia(rT(nT(l) - i) * F5 + (ia(e.x / 1) % 2) * 2) % F5;
return X7[u];
}
const aT = () => {
const e = j.useRef(null),
[t, n] = j.useState({ height: null, width: null });
return (
j.useEffect(() => {
function r() {
n({ height: window.innerHeight, width: window.innerWidth });
}
if (typeof window < "u") return n({ height: window.innerHeight, width: window.innerWidth }), window.addEventListener("resize", r), () => window.removeEventListener("resize", r);
}, []),
j.useEffect(() => {
const r = e.current;
if (!r) return;
const i = 12,
s = ia(t.width / i) * 1.6,
o = ia(t.height / i),
l = { aspect: s / o },
u = setInterval(() => {
let c = "";
for (let d = 0; d < o; d++) {
for (let f = 0; f < s; f++) c += oT({ x: f, y: d }, { cols: s, rows: o, metrics: l, time: Date.now() });
c += `
`;
}
r.textContent = c;
}, 1e3 / sT.fps);
return () => clearInterval(u);
}, [t]),
a.jsx("div", { style: { position: "absolute", top: 0, left: 0, width: "100%", height: "100%" }, children: a.jsx("div", { ref: e, style: { width: "100%", height: "100%", whiteSpace: "pre", overflow: "hidden" } }) })
);
},
通常,我会硬着头皮读完 minify 后的代码,以理解它的实现方式(或者寻找 .map 文件来还原原始代码)。不过,我突然意识到,自己从来没试过让 ChatGPT 替我完成这件事……于是,我复制了上面的所有代码,让 ChatGPT“解释这段代码”。以下是它的回答:
这段代码相当复杂,不过我会把它拆解成更容易理解的形式,并说明各个部分及其功能。
这段代码使用 JavaScript 定义了多个函数和组件,具体来说,它处于 React 应用的上下文中。它负责生成一些动态文本内容,可能是 ASCII art,也可能是某种使用预定义字符集实现的视觉效果。此外,它还包含用于事件处理、性能计时和 HTML 内容转换的工具函数。
字符集选择:const { floor: ra, abs: KE, min: QE } = Math; const O5 = ["reactive.network REACTIVE.NETWORK", "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/|()1{}[]?-_+~<>i!lI;:,^'. .:â–‘â–’â–“â–ˆ"]; const G7 = Date.now() % 3 ? O5[1] : O5[0]; const V5 = G7.length; 这部分代码导入了一些常用的 Math 函数(floor、abs和min),并定义了两组字符,存储在 O5 中。它会根据当前时间(Date.now() % 3)选择其中一组字符(G7),供后续使用。所选字符集的长度存储在 V5` 中。
const { floor: ra, abs: KE, min: QE } = Math;
const O5 = ["reactive.network REACTIVE.NETWORK", "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/|()1{}[]?-_+~<>i!lI;:,^`'. .:â–‘â–’â–“â–ˆ"];
const G7 = Date.now() % 3 ? O5[1] : O5[0];
const V5 = G7.length;
这部分代码导入了一些常用的 Math 函数(floor、abs 和 min),并定义了两组字符,存储在 O5 中。
这部分代码导入了一些常用的 Math 函数(floor、abs 和 min),并定义了两组字符,存储在 O5 中。
它会根据当前时间(Date.now() % 3)选择其中一组字符(G7),供后续使用。
它会根据当前时间(Date.now() % 3)选择其中一组字符(G7),供后续使用。
所选字符集的长度存储在 V5 中。
所选字符集的长度存储在 V5 中。
动态字符选择函数:function eT(e, t, n, r) { const i = t.time * 8e-5; const s = QE(t.cols, t.rows); const o = t.metrics.aspect * 0.2; const l = { x: ((4 * (e.x - t.cols / 6.25)) / s) * o, y: (5 * (e.y - t.rows / 4)) / s }; const u = ra(KE(YE(l) - i) * V5 + (ra(e.x / 1) % 2) * 2) % V5; return G7[u]; } eT 函数会根据输入位置(e.x、e.y)和当前时间(t.time)生成一个字符。它会使用一些宽高比计算来调整位置,并根据位置与时间的组合,从 G7 中选择一个字符。从这段代码本身无法直接确定 YE 函数的用途,但它似乎是该计算过程中使用的一个自定义函数。
function eT(e, t, n, r) {
const i = t.time * 8e-5;
const s = QE(t.cols, t.rows);
const o = t.metrics.aspect * 0.2;
const l = { x: ((4 * (e.x - t.cols / 6.25)) / s) * o, y: (5 * (e.y - t.rows / 4)) / s };
const u = ra(KE(YE(l) - i) * V5 + (ra(e.x / 1) % 2) * 2) % V5;
return G7[u];
}
eT 函数会根据输入位置(e.x、e.y)和当前时间(t.time)生成一个字符。
eT 函数会根据输入位置(e.x、e.y)和当前时间(t.time)生成一个字符。
它会使用一些宽高比计算来调整位置,并根据位置与时间的组合,从 G7 中选择一个字符。
它会使用一些宽高比计算来调整位置,并根据位置与时间的组合,从 G7 中选择一个字符。
从这段代码本身无法直接确定 YE 函数的用途,但它似乎是该计算过程中使用的一个自定义函数。
从这段代码本身无法直接确定 YE 函数的用途,但它似乎是该计算过程中使用的一个自定义函数。
用于显示动态内容的 React 组件(tT):const tT = () => { const e = j.useRef(null); const [t, n] = j.useState({ height: null, width: null }); j.useEffect(() => { function r() { n({ height: window.innerHeight, width: window.innerWidth }); } if (typeof window < "u") { n({ height: window.innerHeight, width: window.innerWidth }); window.addEventListener("resize", r); return () => window.removeEventListener("resize", r); } }, []); j.useEffect(() => { const r = e.current; if (!r) return; const i = 12; const s = ra(t.width / i) * 1.6; const o = ra(t.height / i); const l = { aspect: s / o }; const u = setInterval(() => { let c = ""; for (let d = 0; d < o; d++) { for (let f = 0; f < s; f++) c += eT({ x: f, y: d }, { cols: s, rows: o, metrics: l, time: Date.now() }); c += ; } r.textContent = c; }, 1e3 / JE.fps); return () => clearInterval(u); }, [t]); return a.jsx("div", { style: { position: "absolute", top: 0, left: 0, width: "100%", height: "100%" }, children: a.jsx("div", { ref: e, style: { width: "100%", height: "100%", whiteSpace: "pre", overflow: "hidden" } }) }); }; 这是一个 React 组件,它会设置一个 interval,定期使用动态生成的字符更新 div 元素(e.current)的内容。它根据窗口大小计算列数(s)和行数(o),然后使用 eT 函数生成的字符填充这个 div。该 effect 会按照指定的每秒帧数(JE.fps)运行。
tT)const tT = () => {
const e = j.useRef(null);
const [t, n] = j.useState({ height: null, width: null });
j.useEffect(() => {
function r() {
n({ height: window.innerHeight, width: window.innerWidth });
}
if (typeof window < "u") {
n({ height: window.innerHeight, width: window.innerWidth });
window.addEventListener("resize", r);
return () => window.removeEventListener("resize", r);
}
}, []);
j.useEffect(() => {
const r = e.current;
if (!r) return;
const i = 12;
const s = ra(t.width / i) * 1.6;
const o = ra(t.height / i);
const l = { aspect: s / o };
const u = setInterval(() => {
let c = "";
for (let d = 0; d < o; d++) {
for (let f = 0; f < s; f++) c += eT({ x: f, y: d }, { cols: s, rows: o, metrics: l, time: Date.now() });
c += `
`;
}
r.textContent = c;
}, 1e3 / JE.fps);
return () => clearInterval(u);
}, [t]);
return a.jsx("div", { style: { position: "absolute", top: 0, left: 0, width: "100%", height: "100%" }, children: a.jsx("div", { ref: e, style: { width: "100%", height: "100%", whiteSpace: "pre", overflow: "hidden" } }) });
};
这是一个 React 组件,它会设置一个 interval,定期使用动态生成的字符更新 div 元素(e.current)的内容。
这是一个 React 组件,它会设置一个 interval,定期使用动态生成的字符更新 div 元素(e.current)的内容。
它根据窗口大小计算列数(s)和行数(o),然后使用 eT 函数生成的字符填充这个 div。
它根据窗口大小计算列数(s)和行数(o),然后使用 eT 函数生成的字符填充这个 div。
该 effect 会按照指定的每秒帧数(JE.fps)运行。
该 effect 会按照指定的每秒帧数(JE.fps)运行。
随后,我又让它“用 TypeScript 实现等价代码,并让代码具备良好的可读性”,得到了下面的回答:
import React, { useEffect, useRef, useState } from 'react';
const selectedCharacterSet =
"$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/|()1{}[]?-_+~<>i!lI;:,^`'. .:â–‘â–’â–“â–ˆ";
const characterSetLength = selectedCharacterSet.length;
const calculateCharacter = (
x: number,
y: number,
cols: number,
rows: number,
aspect: number,
time: number,
): string => {
const timeFactor = time * 0.000_08;
const size = Math.min(cols, rows);
const aspectRatio = aspect * 0.2;
const position = {
x: ((4 * (x - cols / 6.25)) / size) * aspectRatio,
y: (5 * (y - rows / 4)) / size,
};
const index =
Math.floor(
Math.abs(
Math.cos(position.x * position.x - position.y * position.y) -
timeFactor,
) *
characterSetLength +
(Math.floor(x) % 2) * 2,
) % characterSetLength;
return selectedCharacterSet[index];
};
type Size = {
height: null | number;
width: null | number;
};
const AsciiArtGenerator: React.FC = () => {
const textRef = useRef<HTMLDivElement>(null);
const [size, setSize] = useState<Size>({ height: null, width: null });
useEffect(() => {
const handleResize = () => {
setSize({ height: window.innerHeight, width: window.innerWidth });
};
// Initial size setting
handleResize();
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
useEffect(() => {
const element = textRef.current;
if (!element || !size.width || !size.height) return undefined;
const cols = Math.floor(size.width / 12) * 1.6;
const rows = Math.floor(size.height / 12);
const aspectRatio = cols / rows;
const intervalId = setInterval(() => {
let content = '';
for (let y = 0; y < rows; y++) {
for (let x = 0; x < cols; x++) {
content += calculateCharacter(
x,
y,
cols,
rows,
aspectRatio,
Date.now(),
);
}
content += '\n';
}
element.textContent = content;
}, 1_000 / 60); // 60 fps
return () => {
clearInterval(intervalId);
};
}, [size]);
return (
<div
ref={textRef}
style={{
height: '100%',
overflow: 'hidden',
whiteSpace: 'pre',
width: '100%',
}}
/>
);
};
export default () => {
return <AsciiArtGenerator />;
};
令我惊讶的是,这个回答不仅已经足够好了,而且可读性也非常高。
对比两份输出后可以看出,LLM 的回答忽略了一些实现细节,但它仍然是一个很值得学习的实现。这绝对是一个我以前从未想到过的巧妙用例!
更新(2024-08-29):最初,我以为 LLM 没有准确复刻原有逻辑,因为输出中缺少了原组件里可见的一些字符(例如 ░▒▓█)。不过,HN 论坛上的一位用户指出,这很可能是复制粘贴导致的错误。
进一步调查后,我发现原始代码中的字符与我粘贴到 ChatGPT 里的并不相同。这似乎是一个编码问题,因为下载脚本后,我成功获得了正确的字符。将代码更新为使用正确字符之后,现在的输出已经与原组件完全一致。
GPT-4,我为自己错误地指责你犯了错而道歉。
作者:punkpeye(@punkpeye)