<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xml" href="/feed.xslt.xml"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-CN"><generator uri="https://jekyllrb.com/" version="3.9.5">Jekyll</generator><link href="/atom.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" hreflang="zh-CN" /><updated>2026-09-07T17:33:40+08:00</updated><id>/atom.xml</id><title type="html">Mayx的博客</title><subtitle>Mayx&apos;s Home Page</subtitle><author><name>mayx</name></author><entry><title type="html">Vibe Coding 新体验（2）</title><link href="/2026/09/01/vibe-coding2.html" rel="alternate" type="text/html" title="Vibe Coding 新体验（2）" /><published>2026-09-01T00:00:00+08:00</published><updated>2026-09-01T00:00:00+08:00</updated><id>/2026/09/01/vibe-coding2</id><content type="html" xml:base="/2026/09/01/vibe-coding2.html">&lt;p&gt;AI is Everything!&lt;!--more--&gt;&lt;/p&gt;

&lt;h1 id=&quot;起因&quot;&gt;起因&lt;/h1&gt;
&lt;p&gt;在两个月前，我&lt;a href=&quot;/2026/07/01/vibe-coding.html&quot;&gt;体验了现在的Vibe Coding&lt;/a&gt;，不过那时候我其实还是没有完全信任AI，认为它的能力可能还不够强，大多数内容依然需要人来做。 &lt;br /&gt;
  但是自从上次做了&lt;a href=&quot;/2026/08/01/terminal.html&quot;&gt;终端版博客&lt;/a&gt;之后，我意识到，它的能力也许比我想象的更强，可能真的能做到超越99%的开发者了，因此在最近一段时间，我一直使用AI做我想做的各种东西，结果就是一次又一次地让我感到难以置信，现在的AI真的实在是太强了😨。而且我做的这一切并不是用最先进的那批AI，仅仅是国内被大家不怎么看好的免费模型——&lt;a href=&quot;https://huggingface.co/tencent/Hy3&quot;&gt;腾讯混元Hy3&lt;/a&gt;，那么我很难想象，如果我能用像GPT 5.6 Sol或者Fable 5那样当下最先进的AI，我做的这些东西得有多完美啊……&lt;/p&gt;

&lt;h1 id=&quot;最近vibe-coding出来的各种项目&quot;&gt;最近Vibe Coding出来的各种项目&lt;/h1&gt;
&lt;h2 id=&quot;blog-api--webmcp&quot;&gt;Blog API + WebMCP&lt;/h2&gt;
&lt;p&gt;在上次做完&lt;a href=&quot;/2026/08/01/terminal.html&quot;&gt;终端版博客&lt;/a&gt;之后，我就在想，用Linux终端来呈现博客的形式真的是个好选择吗？毕竟Linux在浏览器上并不是原生的，尽管用了&lt;a href=&quot;https://github.com/tombl/linux&quot;&gt;WASM Linux&lt;/a&gt;可以做到WASM原生运行Linux，但这还是额外添加的东西。于是我就想，JavaScript应该也能作为终端吧？就像Node.js一样，REPL也可以作为终端使用，正好那段时间DeepSeek-V4-Flash正式版出了，就让它直接写了&lt;a href=&quot;https://github.com/Mabbs/jsnix/blob/main/repl.html&quot;&gt;一个Demo&lt;/a&gt;出来试试看，可以点开&lt;a href=&quot;https://mabbs.github.io/jsnix/repl.html&quot;&gt;这里&lt;/a&gt;体验。 &lt;br /&gt;
  写出来效果还挺不错的，和之前做的终端版博客的主要区别就是我写的是函数，而不是命令，不管怎么说也算是“真的”终端吧，唯一体验不太好的可能就是函数需要带个括号才能运行，稍微有点麻烦。 &lt;br /&gt;
  但后来我想了想，明明开发者工具中就有个控制台，根本没必要搞什么REPL终端啊，直接把要用的功能提出来作为函数，在控制台中运行效果比那个终端的效果要好得多啊，而且这样就可以直接集成到我的博客中，不需要单独搞个页面，感觉体验更好一些。 &lt;br /&gt;
  既然有了函数，我觉得只给人类用就太浪费了，前段时间看到谷歌搞了个&lt;a href=&quot;https://webmachinelearning.github.io/webmcp/&quot;&gt;WebMCP&lt;/a&gt;，那我完全可以把这些函数做成WebMCP让AI来调用啊，相比于其他乱七八糟给AI的协议，至少WebMCP是有谷歌和微软撑腰的，所以加一个也不错。 &lt;br /&gt;
  最终我把WebMCP的文档以及之前实现的REPL代码直接发给AI，让它帮我实现对应的函数以及应用WebMCP API，没几分钟它就把所有功能实现好了，最终代码就是：&lt;a href=&quot;/assets/js/blog-console.js&quot;&gt;blog-console.js&lt;/a&gt;。在开发的过程中，AI严格按照文档中的要求实现了，结果我在实际测试的时候，发现Chrome在实现&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;registerTool&lt;/code&gt;方法的时候和文档的实现有差异，文档说这个方法会返回Promise，但Chrome实际并没有返回，所以AI调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.then()&lt;/code&gt;时直接报错了😓……没办法就只能手动给它套一层Promise，才算是能正常工作。毕竟这还是一个没有正式上线的API，出点问题倒也能理解吧。 &lt;br /&gt;
  虽然东西已经实现了，不过到底怎么才能让其他人知道我用了WebMCP的技术呢？我发现网上有一个WebMCP Directory专门收录使用了WebMCP技术的网站，于是我就随手提交了一下，现在在&lt;a href=&quot;https://webmcp.com/sites/mabbs.github.io&quot;&gt;这里&lt;/a&gt;就能看到我博客的WebMCP信息了。&lt;/p&gt;
&lt;h2 id=&quot;某个即将关服的游戏的离线快照&quot;&gt;某个即将关服的游戏的离线快照&lt;/h2&gt;
&lt;p&gt;最近一段时间我在玩的一个使用Unity3D引擎制作的立直麻将游戏发了篇公告，说是要关服了，还给全服所有玩家送了一堆抽卡资源。我听到这个消息之后感觉还是挺遗憾的，那个游戏总体来说做得还算不错吧，而且画风也符合我的口味，角色模型有点米哈游的那种感觉。我曾经遇到过几次游戏关服的事件，但是每一次我都无能为力，只能看着游戏消失然后逐渐被遗忘。然而现在我有了AI，也许一切会变得不一样吧？这次我要使用AI，让即将消失的游戏不再消失。&lt;/p&gt;
&lt;h3 id=&quot;重放大厅数据&quot;&gt;重放大厅数据&lt;/h3&gt;
&lt;p&gt;最开始我其实想得很简单，游戏登录之后其实没有和服务器有太多的通信，像账号中有的角色以及各种材料等信息在登录的时候就已经发送了，直到进入对局之前客户端都没有和服务器有什么有效的沟通，而我主要需要的也就是能登进去看看角色，听听语音就好了。那么接下来我只需要让AI把整个游戏资源下载下来，然后抓个包，让它给客户端重放就好了，也不需要什么逆向操作。 &lt;br /&gt;
  AI分析了一下之后发现它用的是WebSocket+Protobuf协议，正常来说如果要使用Protobuf协议得要有.proto接口定义文件才可以，不过既然我的要求只是重放，它就没去考虑逆向了，只是简单分析了一下编码规则，就用Python把服务端给写出来了。 &lt;br /&gt;
  不过如果这个快照必须本地启动一个服务器，玩起来就有点麻烦了，这样搞得我估计就没心思玩它了，但是自从我做了终端版博客之后，我觉得浏览器可能没有做不到的事情，既然Python能运行服务器，那JavaScript一样可以，于是我就要求AI让游戏和服务端一起运行在浏览器中。 &lt;br /&gt;
  结果它还真折腾出来了，在JavaScript中似乎一切都可以被覆盖，于是AI就替换了浏览器的WebSocket对象，让客户端以为它连接到了真实的服务器，然后再按照之前录制的包发送就好了，折腾了一段时间之后游戏真的能正常启动了，真的是太离谱了😨。&lt;/p&gt;
&lt;h3 id=&quot;制作对局引擎&quot;&gt;制作对局引擎&lt;/h3&gt;
&lt;p&gt;其实这基本上已经满足我的要求了，但我还是有点好奇，这个客户端里真的没有.proto接口定义文件吗？正常来说既然客户端知道怎么和服务端通信，那么这个文件应该是必须要有的吧？反正目的已经达到了，接下来就让AI瞎折腾看看。 &lt;br /&gt;
  在我要求完之后它又折腾了好长时间，结果.proto也被它给搞出来了😨，也许对于会逆向Unity3D游戏的人来说这不算什么，但这已经让我很震惊了……既然有了这些，也许完成一个完整的服务端将不再是难事。 &lt;br /&gt;
  于是我让它录制了几把我实际对局的包，并理解整个对局中数据包的时序，开始考虑如何做出完整的麻将对局。首先作为离线运行的游戏来说，和人对局肯定是做不出来了，那就得想办法搓个人机出来。我最先想到的是著名的麻将AI &lt;a href=&quot;https://github.com/Equim-chan/Mortal&quot;&gt;Mortal&lt;/a&gt;，之前用它在雀魂上打了几把之后结果号没了😂，不过我确实觉得和人打麻将没什么意思，感觉麻将如果全按最优解打的话结果就全是靠运气……不过后来我想了一下还是不用Mortal了，首先这个模型不小，整个模型要好几百MiB，而且三麻和四麻的模型还不一样，还有三麻的模型还是收费的……另外真和Mortal打的话感觉难度也确实有点高，除了运气因素外，至少人机犯错的概率比人要低不少，那打起来估计会很难受。所以我就对AI说，做一个降低向听数为主的全攻型人机，其他细节让它自己考虑，另外我想到上次做的那个麻将Wordle它自己写的役种分析实在是有点烂，所以这次就告诉它必须要使用现成的&lt;a href=&quot;https://github.com/takayama-lily/riichi&quot;&gt;Riichi&lt;/a&gt;库，免得出乱七八糟的问题。 &lt;br /&gt;
  在连续跑了好几个小时之后AI成功把完整的&lt;a href=&quot;https://github.com/Mabbs/mahjong-dream-snapshot/blob/main/mock/riichi.js&quot;&gt;对局引擎&lt;/a&gt;给搓出来了，真的是相当了不起，要知道这个东西正常来说人开发怎么说也得好几周吧？它就几个小时开发出来了，还理解了数据包的时序，用之前分析的.proto文件成功接入到了游戏中，实在是太强了……当然因为我没有用大量真实牌谱去测试，有BUG也是正常的，比如目前的引擎还存在立直之后完全没法开杠这样的边缘问题，就只能慢慢修了，不过这都是小问题，它能做到目前的水平已经不是一般人能做到的了。 &lt;br /&gt;
  最终做出来的项目可以在&lt;a href=&quot;https://github.com/Mabbs/mahjong-dream-snapshot&quot;&gt;这里&lt;/a&gt;看到，还可以通过&lt;a href=&quot;https://mabbs.github.io/mahjong-dream-snapshot/&quot;&gt;GitHub Pages&lt;/a&gt;上的部署直接游玩。&lt;/p&gt;
&lt;h3 id=&quot;隐藏彩蛋&quot;&gt;隐藏彩蛋&lt;/h3&gt;
&lt;p&gt;在AI做完整个服务端之后，我闲得没事瞎改对战人机的角色和皮肤，因为游戏中的皮肤ID有一定的规律，所以可以根据角色ID来猜皮肤ID。在这个时候，我发现其中有一个角色在游戏中没有对应的泳装皮肤，但是我把人机的角色按照规律配置推测的泳装皮肤ID是能显示出来的……那也就是说这个游戏可能在配置中隐藏了这个皮肤，但是并没有删掉美术资源，所以接下来我就想着能不能让AI把这个隐藏的皮肤显示出来，这样就能在游戏中直接使用了。 &lt;br /&gt;
  所以我根据自己推测的信息，告诉AI：在这个游戏中有一个皮肤编号3708002，头像编号1018的资源被隐藏了，其中关于这些资源的配置应该在commonconfigs中，帮我解包并分析，考虑如何将这个皮肤和头像恢复显示，然后重新打包资源，使其可以正常加载。就这么个需求，它从Bundle的解包，到分析FlatBuffers，然后又处理XAsset的校验……最终花了3个小时就给我做出来了，而且我试了一下，非常完美，简直就像这个皮肤本来就存在一样，不过原本这就是被官方隐藏的嘛，解除隐藏自然就和真的存在一样啊😂。&lt;/p&gt;
&lt;h2 id=&quot;能在wasm-linux中运行的ssg&quot;&gt;能在WASM Linux中运行的SSG&lt;/h2&gt;
&lt;p&gt;之前我做完终端版博客之后就在想，让这个完整的Linux只用来展示博客似乎有点浪费，感觉它还能做更多事，但对我来说编译在这个系统中能运行的二进制程序还是有一点困难，虽然我有用AI，但是我的Agent是运行在macOS上的，所以编译Linux二进制程序有点困难……不过单是BusyBox其实就能做相当多的事情了，毕竟我当年可是在手机上用ash做出了论坛系统&lt;a href=&quot;https://github.com/Mabbs/Mabbs.project&quot;&gt;Mabbs&lt;/a&gt;，那它能做的事情应该还有很多。 &lt;br /&gt;
  最近我在和群友聊天的时候发现有人用C#做了个静态网站生成器，也是用AI做的，看起来挺有意思。其实SSG的原理还是挺简单的，说到底就是些文字转换罢了。于是我就想，要不然我就让AI搓个能在WASM Linux中运行的SSG出来吧？ &lt;br /&gt;
  当然直接搓我还是会有一点怀疑AI的能力，所以又找了点素材，之前在&lt;a href=&quot;/2025/08/10/tilde.html&quot;&gt;体验Tilde社区&lt;/a&gt;的时候发现了&lt;a href=&quot;https://github.com/cfenollosa/bashblog&quot;&gt;bashblog&lt;/a&gt;，其实这就已经是一个现成的SSG了，只不过它用的是Bash语法，得让AI转换成POSIX风格的语法。另外它还依赖使用Perl语言编写的&lt;a href=&quot;https://daringfireball.net/projects/markdown/&quot;&gt;Markdown库&lt;/a&gt;，我这个系统里可没有Perl啊，也得想办法找个可以替代的东西。 &lt;br /&gt;
  我想了一下，在BusyBox中最适合处理文本的大概就是awk了，所以我首先应该找找有没有用awk编写的Markdown转换程序。结果搜了一下还真被我找到了，有一个叫做&lt;a href=&quot;https://github.com/wernsey/d.awk&quot;&gt;d.awk&lt;/a&gt;的库，里面包含一个完全用awk写的Markdown转换程序，那么接下来我只需要把这两个项目丢给AI，让它以bashblog为基础，以d.awk为引擎制作一个兼容性极强的POSIX SSG吧。 &lt;br /&gt;
  这种问题果然还是太简单了，它只用了一两个小时的时间，就真的做出来了，最终它把这个项目起名叫做&lt;a href=&quot;https://gist.github.com/Mabbs/3db745dc403d752a1d59d3e06938e310&quot;&gt;possg&lt;/a&gt;，并且我把这个程序粘贴到了我的终端版博客中运行了一下，效果也是非常完美……如果哪一天我抛弃了Jekyll，也许就会用这个possg吧😂。&lt;/p&gt;
&lt;h2 id=&quot;终端版博客的各种新功能&quot;&gt;终端版博客的各种新功能&lt;/h2&gt;
&lt;p&gt;最近一段时间我在我的&lt;a href=&quot;https://github.com/Mabbs/linux&quot;&gt;终端版博客&lt;/a&gt;又有一些新的想法，于是就对着AI反复许愿，结果AI还真的能让愿望成真。以下就是从上次做完之后又新增的一些功能：&lt;/p&gt;
&lt;h3 id=&quot;markdown高亮&quot;&gt;Markdown高亮&lt;/h3&gt;
&lt;p&gt;最初博客在终端中显示文章时是直接打印的Markdown原文，感觉不太好看，所以我就想着如果能加个高亮功能体验应该会更好吧？最开始我要求AI做这个功能的时候它选择使用Shell+sed来实现，那时候我还没有做possg，没考虑用awk来做，所以性能非常差。因为Shell在处理这种任务的时候要不停地调用sed，这个系统开进程的开销又很大，所以处理一篇文章得好几秒，后来才考虑用awk来做，结果性能确实好了不少，最终的代码可以在&lt;a href=&quot;https://github.com/Mabbs/linux/blob/initramfs/bin/mdhl&quot;&gt;这里&lt;/a&gt;查看。&lt;/p&gt;
&lt;h3 id=&quot;挂载本地文件夹&quot;&gt;挂载本地文件夹&lt;/h3&gt;
&lt;p&gt;前段时间我看到作者合并了一个关于&lt;a href=&quot;https://github.com/tombl/linux/pull/40&quot;&gt;virtiofs的PR&lt;/a&gt;，当时我看到这个PR的时候就想，记得Chrome浏览器有一个&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/API/Window/showDirectoryPicker&quot;&gt;showDirectoryPicker API&lt;/a&gt;，可以允许网页对指定的文件夹进行读写。现在有了这个驱动的话，让AI做个将本地文件夹挂载到Linux的功能应该是个很简单的事情，于是我直接就闭着眼睛让AI做了。 &lt;br /&gt;
  做完之后虽然能用，但是我又回想起上次让AI搓的通过Service Worker访问系统内网络服务的功能，结果人家原作者已经做了一样的功能，我还给作者发了&lt;a href=&quot;https://github.com/tombl/linux/issues/34&quot;&gt;Issue&lt;/a&gt;推荐😅，所以这次我得吸取教训看看作者是不是已经做了这个功能，毕竟作者实现virtiofs的时候估计就有这个打算吧？结果看了一眼还真&lt;a href=&quot;https://github.com/tombl/distro/blob/main/packages/linux-guest/src/browser.ts&quot;&gt;做了&lt;/a&gt;😆，我闭着眼睛让这个垃圾AI去做看来确实有点着急了，虽然我不知道原来我的AI做得到底怎么样，但是我对它还是没什么信心，也许作者这个代码也是AI写的，但很明显他的AI比我的AI水平强得多，所以让它参考作者做的这个版本重新写了一版。总之最终都能用，写出来的代码就是：&lt;a href=&quot;https://github.com/Mabbs/linux/blob/main/hostfs.js&quot;&gt;hostfs.js&lt;/a&gt;，再配合上&lt;a href=&quot;https://github.com/Mabbs/linux/blob/initramfs/bin/mount_host&quot;&gt;mount_host&lt;/a&gt;脚本就可以实现挂载本地文件夹的功能了。&lt;/p&gt;
&lt;h3 id=&quot;基于webrtc的局域网内组网&quot;&gt;基于WebRTC的局域网内组网&lt;/h3&gt;
&lt;p&gt;我在制作网络模块的时候一直在想，有没有办法能在不依靠服务器的情况下让两个这样的Linux系统互联呢？正常来说要想让两个浏览器内的服务互相访问就必须有一个中转服务器，因为浏览器肯定是不允许网页监听端口，而且就算能监听，我的这个网页还是放在GitHub Pages上，使用了HTTPS，也没办法访问HTTP资源，搞证书也是个麻烦事。 &lt;br /&gt;
  但其实浏览器是有P2P功能的，那就是&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/API/WebRTC_API&quot;&gt;WebRTC&lt;/a&gt;，只是我不太确定这个技术能不能做到无服务器，于是我问了问AI，这个功能能不能不要服务器，结果AI说可以，只要两个机器在同一个局域网，就可以通过手动交换SDP信令来实现P2P连接，而且考虑到我基本上完全没有锥形NAT网络可以用，所以就不搞STUN服务器了，反正也连不上。那既然理论上可以的话，就让AI实现吧。 &lt;br /&gt;
  最终花了一点时间AI就写出来了，使用&lt;a href=&quot;https://github.com/Mabbs/linux/blob/main/webrtc-p2p.js&quot;&gt;webrtc-p2p.js&lt;/a&gt;配合&lt;a href=&quot;https://github.com/Mabbs/linux/blob/initramfs/bin/connector&quot;&gt;connector&lt;/a&gt;就可以实现同一个局域网内两台电脑上的浏览器中的Linux互联了。 &lt;br /&gt;
  这个功能做出来之后直接就有了无限的想象空间，比如想用A电脑的浏览器控制B电脑浏览器中的Linux，就可以在B电脑中执行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tcpsvd -v 0.0.0.0 4444 /usr/bin/script -qc &apos;stty -echo; /bin/sh -i&apos; /dev/null&lt;/code&gt;，然后在A电脑用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nc 10.0.2.x 4444&lt;/code&gt;去连接就可以了，效果就像SSH一样，而且配合之前做的挂载本地文件夹功能，甚至可以直接操作B电脑中的文件；另外之前做的Service Worker访问系统内网络服务也能用得上，假如B电脑有一个网页想要分享给A电脑，可以在B电脑上用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mount_host&lt;/code&gt;把本地文件夹挂载到Linux的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/mnt/host&lt;/code&gt;目录中，然后在那个目录执行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;httpd -p 8080&lt;/code&gt;，A电脑可以执行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tcpsvd -v 0.0.0.0 8080 /usr/bin/nc 10.0.2.x 8080&lt;/code&gt;转发，接下来打开&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http/8080/&lt;/code&gt;就可以访问到B电脑上的网页了。 &lt;br /&gt;
  总之只要有了网络，这个系统的功能就能被无限扩大了，只是……说是这么说，其实还是依赖浏览器的功能，所以这些功能就算用JavaScript直接做也完全没问题，但毕竟是作为Linux终端，有了这些能力之后就显得更完整了。 &lt;br /&gt;
  最终我仔细确认了原作者确实没有做这个功能，所以就发了个&lt;a href=&quot;https://github.com/tombl/distro/issues/156&quot;&gt;Issue&lt;/a&gt;看看他有没有兴趣把这个功能加到他的库中，如果被接受了也算是做出贡献了吧。&lt;/p&gt;
&lt;h3 id=&quot;使用vless协议的网关&quot;&gt;使用VLESS协议的网关&lt;/h3&gt;
&lt;p&gt;虽然之前已经加了基于浏览器Fetch API的网关，但这个网关的局限性还是挺大的，基本上也就只能请求网站了。那其他能在浏览器上运行系统的项目是怎么做的呢？看了一下像v86用的是一个基于L2 Over WS的公共网关relay.widgetry.org，而WebVM用的则是Tailscale。这两种我都不喜欢，主要是这些东西基本上专门服务于这类在浏览器中运行的系统，普通人并不会特意去搭建，而我既不想用某个特定的服务，也不想自己搭……那么有什么样的网络服务既随处可见，又能在浏览器中使用呢？这时候我就想到了VLESS协议，它天生就适合运行在WebSocket中，而且搭建起来极其简单，比如有人用Cloudflare Workers做了一个叫做&lt;a href=&quot;https://github.com/zizifn/edgetunnel&quot;&gt;edgetunnel&lt;/a&gt;的脚本，可以轻松将Cloudflare作为代理使用，因此我决定接下来让AI实现一个基于VLESS+WSS的网关，这样除了请求网站之外还能做点别的事情，比如用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;telnet towel.blinkenlights.nl&lt;/code&gt;命令看ASCII版的《星球大战》😆。 &lt;br /&gt;
  只不过相比于其他项目用的那些L2的服务来说，VLESS承载的是L4的协议，所以我还得让AI实现TCP/IP协议栈……其实我不太想这样做的，因为AI做这种事情容易出错，但是它看了一下我之前用的&lt;a href=&quot;https://github.com/chipmk/tcpip.js&quot;&gt;tcpip.js&lt;/a&gt;库说是里面的API不满足要求……那没办法，AI既然自己想搓就让它搓吧。不过VLESS协议这个我觉得还是得给它个&lt;a href=&quot;https://github.com/XTLS/Xray-docs-next/blob/main/docs/en/development/protocols/vless.md&quot;&gt;文档&lt;/a&gt;，免得它开始瞎编。 &lt;br /&gt;
  不管怎么说它还是搓出来了，虽然质量我不是特别满意……但毕竟手头用的AI不是最先进的AI，就先这样吧～最终的代码就是：&lt;a href=&quot;https://github.com/Mabbs/linux/blob/main/vless-gateway.js&quot;&gt;vless-gateway.js&lt;/a&gt;，用法也很简单，首先要有一个&lt;strong&gt;证书合法&lt;/strong&gt;的VLESS+WSS节点，然后在我的博客终端中按F12打开控制台，输入：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;switchToVless(&quot;wss://domain:port/path&quot;, &quot;UUID&quot;)&lt;/code&gt;就可以将原来基于Fetch的网络切换到VLESS节点的网络了，考虑到避免被滥用的情况，我就不内置节点了，如果有人有兴趣可以用自己的节点进行连接。&lt;/p&gt;

&lt;h1 id=&quot;感想&quot;&gt;感想&lt;/h1&gt;
&lt;p&gt;做到这里，可以感受到最近的AI所做的一切真的是令人难以置信，它不仅知道几乎所有知识，还有不知疲倦的思考能力，以及足够的动手能力，而这也仅仅建立在一个很一般的免费模型上。再回想起前段时间用的Agent，做的什么&lt;a href=&quot;/2025/04/08/feed.html&quot;&gt;订阅源预览&lt;/a&gt;，简直就太简单了😂。目前据我所知其他人使用更先进的AI已经可以解决人类没能解决的数学难题、自主设计处理器指令集架构在上面运行Doom、&lt;a href=&quot;https://github.com/Kelsidavis/System7&quot;&gt;从零开发一个Classic Mac OS&lt;/a&gt;、制作直接在UEFI中运行的软件、以及完全逆向Unity3D游戏，把几款游戏混合起来做成一个新游戏……（至于网上总传的什么DeepSeek的所谓的灰测模型搞出来的&lt;a href=&quot;https://github.com/6WENHAO/DS-Games&quot;&gt;各种Demo&lt;/a&gt;就算了，根本比不了）过去各种难以想象的东西都被轻松做出来了，而这一切就发生在最近几个月，我已经很难想象未来AI能做到什么样的地步了，也许AI真的会带领人类走向无法想象的高度吧～&lt;/p&gt;</content><author><name>mayx</name></author><category term="Vibe Coding" /><category term="AI" /><category term="Agent" /><summary type="html">AI is Everything!</summary></entry><entry><title type="html">一次制作终端版博客的经历</title><link href="/2026/08/01/terminal.html" rel="alternate" type="text/html" title="一次制作终端版博客的经历" /><published>2026-08-01T00:00:00+08:00</published><updated>2026-08-01T00:00:00+08:00</updated><id>/2026/08/01/terminal</id><content type="html" xml:base="/2026/08/01/terminal.html">&lt;p&gt;在浏览器上用真的Linux访问博客会是什么样的感觉？&lt;!--more--&gt;&lt;/p&gt;

&lt;h1 id=&quot;起因&quot;&gt;起因&lt;/h1&gt;
&lt;p&gt;在去年的时候我为了想在博客上加个终端，写过一篇关于&lt;a href=&quot;/2025/12/01/linux.html&quot;&gt;在浏览器中运行Linux&lt;/a&gt;的文章。不过那时候只是调查了有哪些可能的方法，并没有真的做出来，主要是自己做感觉太麻烦了😂。 &lt;br /&gt;
  但从上次进行了&lt;a href=&quot;/2026/07/01/vibe-coding.html&quot;&gt;Vibe Coding&lt;/a&gt;之后，我意识到这可能不再是什么麻烦事，也许只要和AI聊聊天就能轻松做出来了，于是接着之前的方案，我开始着手完成我的想法。&lt;/p&gt;

&lt;h1 id=&quot;终端版博客的不同形式&quot;&gt;终端版博客的不同形式&lt;/h1&gt;
&lt;h2 id=&quot;基于wasm-linux的版本&quot;&gt;基于WASM Linux的版本&lt;/h2&gt;
&lt;p&gt;在之前的文章里，我其实更倾向于WASM Linux的方案，尤其是&lt;a href=&quot;https://github.com/tombl&quot;&gt;Thomas Stokes&lt;/a&gt;制作的&lt;a href=&quot;https://github.com/tombl/linux&quot;&gt;WASM Linux&lt;/a&gt;，因为它相比于虚拟机更轻量，效率更高，而且还能完美地在Safari中运行（毕竟我用的是macOS😂），因此接下来我计划用它为基础制作我的终端版博客。 &lt;br /&gt;
  不过之前我也说过，目前这个WASM Linux还不能和外界通信，不能和外界通信就不能读取我的文章。于是我打算先让AI解决这个问题。但如果我直接给它说想要和外界通信也许它会给我整一堆奇怪的实现，所以我得自己先想一个实现起来最简单的方案。之前我记得Fabrice Bellard大神写的JSLinux中有一个剪贴板设备文件可以和外部通信，那么干脆让AI写一个能在浏览器中执行JS的设备文件吧，这样做出来只需要写简单的Shell脚本就可以和浏览器交互了，也不需要考虑如何编译WASM程序，于是就这样我开始让它实现这个功能。 &lt;br /&gt;
  最终经过几次调试和修改之后AI真的把这个设备文件给做出来了，不过刚做出来的时候有些问题。因为在这个Linux中的进程都是在Web Worker中运行，执行的JS也会在Web Worker中运行。但是Web Worker的局限性很大，并不能和当前网页进行交互，不过这种问题也都是小事，发现了之后只要给AI一反馈也是轻轻松松就改好了……最终实现的效果我感觉相当不错，既然如此，那我就把AI做的这个驱动给上游&lt;a href=&quot;https://github.com/tombl/linux/pull/24&quot;&gt;PR&lt;/a&gt;一下吧，也算做个贡献。不过最终原作者不太喜欢这种实现方式，他好像更喜欢标准化的方式，说是已经做了一个&lt;a href=&quot;https://github.com/tombl/linux/blob/b36416c7f266a465361ab7dde7622eb37239cd7e/tools/wasm/src/virtio.ts#L354&quot;&gt;VsockDevice&lt;/a&gt;，可以直接使用Linux自带的驱动和外界通信，所以把我的PR拒了（看来AI Slop还是入不了大佬的法眼😂）。 &lt;del&gt;不过我问了一下AI，它说用这个设备的话可能需要&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ioctl()&lt;/code&gt;系统调用，就没办法直接用Shell操作了，只能用别的语言写的程序才能用它通信，所以我只好自己用这种不标准的方式了。&lt;/del&gt; 虽然VsockDevice确实是不太好操作，但是大佬随后又新增了一个ethernetDevice可以用来上网，考虑到维护自己的分支实在是太麻烦了，所以我让AI写了一个虚拟的HTTP服务，使其能够执行JS，顺便还增加了连接互联网的功能。这样我就可以直接使用上游的代码，不需要再维护之前写的驱动了（其实以太网比Vsock复杂多了，只是因为可以用wget之类的客户端请求所以才简单😋）。 &lt;br /&gt;
  在做好与外界通信的通道后我就开始让AI写能和我博客交互的脚本，不过在这个过程中也遇到了不少问题，最开始的时候发现这个Shell的管道有些问题，执行的时候会忽略管道前的命令，后面的命令就没法执行了……感觉有点奇怪，所以我要求它在实现交互脚本时不要使用管道符。除此之外我还发现反引号和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$()&lt;/code&gt;这些能把执行结果插入字符串的语法也不能用……总之就是局限相当多，但它还是克服了这些困难最终把脚本给写出来了，最终实现了列出文章、查看文章、打开文章和查看评论等功能，效果还是相当不错。 &lt;del&gt;唯一的缺陷就是它不能进行异步操作，只能同步请求，所以在进行请求的时候会把浏览器卡住，体验稍有打折。&lt;/del&gt; 后来用了网络版的JS执行服务后就可以异步了，避免出现把浏览器卡住的情况。 &lt;br /&gt;
  不过这个系统如果管道符不能用的话效果会差一大截，这可是Shell的灵魂啊，所以我提交了一个&lt;a href=&quot;https://github.com/tombl/linux/issues/25&quot;&gt;Issue&lt;/a&gt;，希望作者能修一下。结果作者真的强得离谱，提出问题之后第二天就修复了，实在是太强了😨。但修好这个问题之后作者又整了一堆别的花活，把内存限制给取消了，结果我在Safari上一运行就报OOM的错误……于是我把这个问题又&lt;a href=&quot;https://github.com/tombl/linux/pull/26#issuecomment-5018155963&quot;&gt;反馈&lt;/a&gt;了一下，顺便让AI看看这个问题怎么解决，结果AI也不辜负我的期待，轻轻松松就解决了🤣。最终我除了会提出问题之外什么也不会做，只能看着大佬和AI完成我的想法了😝。 &lt;br /&gt;
  后来增加的以太网网关也挺折腾，原理其实并不复杂，但直接给AI说想用这个接口上网的时候它会自己想办法搓一套漏洞百出的TCP/IP协议栈，所以后来我在GitHub找了个&lt;a href=&quot;https://github.com/chipmk/tcpip.js&quot;&gt;tcpip.js&lt;/a&gt;库让它集成，BUG就比它自己手搓的少了不少。后来我又想加一个TLS终止功能，因为在浏览器上没办法直接让系统里的程序发送原始的TLS数据包去请求网站，但我又希望它能够请求带有https协议的网址，所以要提前对TLS进行解密再转换成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch()&lt;/code&gt;，这时候我就没让AI自己手搓了，在网上找了个&lt;a href=&quot;https://github.com/digitalbazaar/forge&quot;&gt;node-forge&lt;/a&gt;库让AI对接，就可以避免出现一堆奇怪的BUG了。 &lt;br /&gt;
  做好网关之后我又突发奇想，想做一个展示终端内状态的页面，但是目前JS并不能主动和里面的系统直接通信，所以又让AI搓了一个通过Service Worker访问系统内网络服务的功能，然后用busybox的httpd执行sh写的cgi程序，就可以被前端读取到了。随后我还给原作者发了个&lt;a href=&quot;https://github.com/tombl/linux/issues/34&quot;&gt;Issue&lt;/a&gt;展示这个Service Worker的效果，结果人家已经做了这个功能，我还是太Naive了😂。 &lt;br /&gt;
  最终做好的网站可以在&lt;a href=&quot;https://mabbs.github.io/linux/&quot;&gt;这里&lt;/a&gt;查看，效果真的不是一般的好，相比于其他模仿终端形式的博客简直是降维打击，毕竟这是真的Linux啊～虽然目前依然有些BUG，但是原作者修BUG的速度还是挺快的，相信等他发布正式版之后，体验会更进一步。&lt;/p&gt;

&lt;h2 id=&quot;基于jsnix的版本&quot;&gt;基于JSNix的版本&lt;/h2&gt;
&lt;p&gt;虽然我觉得追求真实感很重要，但是我又回去看了一下为我提供想法的&lt;a href=&quot;https://github.com/Erzbir/jsnix&quot;&gt;JSNix&lt;/a&gt;。虽然这个项目并不是真的Linux，但是我让AI看了一遍之后它对这个项目的评价也很高，说是作者对Linux的了解非常深入，很多细节模拟的也非常到位。而且为WASM Linux制作的那些脚本，在这个JSNix上其实完全可以实现，还更简单，于是我直接让AI参考给WASM Linux写的脚本把那些功能全部移植到这个JSNix上。也许是因为AI更擅长编写网页，很快它就完成了这些功能，而且效果非常不错，基本上和WASM Linux上面的效果一样，表现也相当不错，最终生成的结果可以在&lt;a href=&quot;https://mabbs.github.io/jsnix/&quot;&gt;这里&lt;/a&gt;查看。 &lt;br /&gt;
  不过假的毕竟还是假的，和真的Linux差异还是有不少的，像正经的Shell可以在终端中直接用if、for和while之类的语法写一些东西，它这个就必须要编写成脚本才可以，而且解析也有相当多的问题。各种命令的运行结果很多都是硬编码的，只是编了一段对应命令的输出，参数做得也不全。另外著名的Linux文本三剑客grep、sed和awk在这个系统里实现得也非常差，只做了一些非常基础的功能，基本上就是完全不可用。所以说这个系统还是不够真实，只能做最基础的体验，不能够深究。 &lt;br /&gt;
  但并不是说它没有优点，它作为纯粹的JS不像WASM Linux那样容易出现兼容性问题，比如很多手机浏览器不支持跨源隔离，导致SharedArrayBuffer之类的API没法用，所以手机上只能体验这个JSNix的版本；另外WASM Linux的体积虽然说并不算很大，但也有好几MiB，不像这个JSNix连1MiB都不到；还有这个系统没有启动过程，相比于WASM Linux来说加载更快；而且在编写和修改功能的时候比WASM Linux简单不少，像给WASM Linux做的终端状态展示在JSNix上根本不需要折腾什么Service Worker，以及请求网络之类的也非常简单，直接&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch()&lt;/code&gt;就可以（也正是如此，JSNix的版本AI没一会儿就做完了）……总之就是只要不涉及具体功能方面JSNix都比WASM Linux要强不少，不过这样说有点“抛开事实不谈”的感觉了😂。&lt;/p&gt;

&lt;h1 id=&quot;感想&quot;&gt;感想&lt;/h1&gt;
&lt;p&gt;最终做出来的这两个版本的终端版博客基本上所有的代码都不是我写的，真的做到了仅仅是和AI聊聊天就把东西做出来了……只能说AI现在真的不是一般的强啊～当然方案还是得自己考虑，因为AI一旦走错了方向就很容易往一个方向不停地钻，结果就是花了很长时间可能效果会很差，尤其是现在的AI还特别喜欢自己造轮子，所以这时候还是自己找找看已经有的方案然后告诉它会比较好。 &lt;br /&gt;
  从效果来说这次写的东西也让我非常满意，我想我这次做的这个博客界面估计是前所未有的，毕竟这可是在浏览器上用&lt;strong&gt;真的&lt;/strong&gt;Linux访问博客啊，其他人做的都是假的。至于虚拟机那些方案可能也是因为太重，所以基本上没人用，而WASM Linux只有Thomas Stokes做的效果最好，但是又没有受到应有的关注……总之从我个人的角度来看这次做的东西还挺Cool，就是不知道会不会吸引其他人来看呢……可能还是太小众了吧😂。&lt;/p&gt;</content><author><name>mayx</name></author><category term="WASM" /><category term="终端" /><category term="Linux" /><category term="博客" /><summary type="html">在浏览器上用真的Linux访问博客会是什么样的感觉？</summary></entry><entry><title type="html">Vibe Coding新体验</title><link href="/2026/07/01/vibe-coding.html" rel="alternate" type="text/html" title="Vibe Coding新体验" /><published>2026-07-01T00:00:00+08:00</published><updated>2026-07-01T00:00:00+08:00</updated><id>/2026/07/01/vibe-coding</id><content type="html" xml:base="/2026/07/01/vibe-coding.html">&lt;p&gt;All in AI!&lt;!--more--&gt;&lt;/p&gt;

&lt;h1 id=&quot;起因&quot;&gt;起因&lt;/h1&gt;
&lt;p&gt;前段时间，我写了一篇关于&lt;a href=&quot;/2026/04/14/ai-agent.html&quot;&gt;AI Agent&lt;/a&gt;的文章，虽然当时我说这并没有什么创新之处，但在那之后用着用着感觉还是挺好用的，至少相比以前&lt;a href=&quot;/2025/04/08/feed.html&quot;&gt;在云端Vibe Coding&lt;/a&gt;来说又快又免费，所以我打算分享一下最近我用AI Agent的经历。 &lt;br /&gt;
  不过大多数情况下我写代码都可以直接让各路AI在对话中给我生成，那到底有什么样的场景才需要用AI Agent呢？一般应该就是需要边改边测的东西吧。&lt;/p&gt;

&lt;h1 id=&quot;关于pjax的改造&quot;&gt;关于PJAX的改造&lt;/h1&gt;
&lt;p&gt;虽然说AI生成的东西可靠性并不是很高，但至少在网上已经有很多例子的代码应该不至于乱生成吧……其实我一直都想给我的博客增加全站无刷新的功能，但是每次看到PJAX都要自己重新调用当前页面的各种函数就懒得动手了😂。不过其实这也不是什么复杂的事情，主要是一个一个分析函数比较麻烦，所以既然有AI了，不如直接让AI帮我做吧。于是我让它克隆了我的仓库，分析并写出PJAX的代码。 &lt;br /&gt;
  用AI Agent最大的好处就是能让它了解整个项目的所有代码，如果是直接在对话里让AI修改的话就很难做到这一点。总之我告诉它之后等了一会它真的写出来了，我试了一下确实能用，虽然刚开始有不少问题，主要是因为我用了&lt;a href=&quot;/Live2dHistoire/&quot;&gt;Live2D&lt;/a&gt;，不少问题也是这个功能引起的。不过只要我告诉它问题在哪里，它总是能给我改好，先不论它实现的代码怎么样，但至少从效果来说还是相当不错的。 &lt;br /&gt;
  最终生成的代码我看了一下，代码质量非常糟糕，把我在别处的代码到处复制粘贴到PJAX用的代码里了，但是体验效果不错……所以没办法，虽然代码很糟糕，但先用着吧。不过我觉得优化应该也能交给AI Agent，只是它有时候改来改去还会把正常代码改坏，所以优化看起来还是只能自己做…… &lt;br /&gt;
  最终的代码就是：&lt;a href=&quot;/assets/js/pjax.js&quot;&gt;pjax.js&lt;/a&gt;，虽然我对这个代码质量不是很满意，但是毕竟能用，效果也不错，就先这样用吧～&lt;/p&gt;

&lt;h1 id=&quot;生成一个小游戏&quot;&gt;生成一个小游戏&lt;/h1&gt;
&lt;p&gt;前段时间，我在Bilibili上看视频的时候发现了一个有趣的微信小游戏，叫做“雀谜”，它把立直麻将和Wordle两个玩法结合到一起，看起来挺有意思。不过它作为微信上的小游戏有些问题，一是看提示要看广告，二是换题也要看广告，而且看广告就算能换题也有次数限制，一天也就几次机会，让我有点不爽。 &lt;br /&gt;
  不过从功能上来说看起来并不复杂，感觉用AI直接Vibe Coding完全不是问题，不过AI Agent从零开始写比较考验模型的水平，所以我打算先让各路网页上的AI生成初版的Demo，之后再考虑怎么改。于是我对它们说：&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;帮我制作一款立直麻将Wordle网页小游戏，规则为：&lt;/p&gt;
  &lt;ol&gt;
    &lt;li&gt;系统随机生成一个有役能和的14张牌，必须按照万、筒、索、字的顺序排列，同花色内数字从小到大，但是第14张用来和的牌可以不遵守排列规则&lt;/li&gt;
    &lt;li&gt;让玩家猜6次来猜到这个牌型，每次猜的牌也必须是满足有役和牌的条件&lt;/li&gt;
    &lt;li&gt;位置和内容正确的牌用绿色标注、只有内容正确但是位置不正确的用黄色标注，不在生成范围内的牌用灰色标注&lt;/li&gt;
    &lt;li&gt;界面中所有的牌使用Unicode中的麻将字符，并且提供一组输入按钮用于输入这些麻将字符&lt;/li&gt;
    &lt;li&gt;输入按钮在游玩过程中也要标注颜色&lt;/li&gt;
    &lt;li&gt;随机生成的牌需要标注场风、自风和是否自摸，如果是荣和需要额外的役，需要考虑平和之类的役种&lt;/li&gt;
    &lt;li&gt;加一个提示键，可以查看答案牌型的役种&lt;/li&gt;
  &lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;直接生成确实很考验水平，有很多AI直接生成出来的完全用不了，像DeepSeek生成出来的按钮按上去都没有反应😅，最终只有Gemini生成出来的感觉还不错，无论是界面还是输入的感觉都很好，于是接下来的修改我就打算以Gemini生成出来的代码为主。 &lt;br /&gt;
  刚生成出来的代码虽然能玩，但是每个AI直接生成都不想做完整的役种检测，说什么一个对话不够😥，明明各个都是几百k上下文的AI，有什么不够的……总之想一口气就完美做出来显然是不现实的，不过其实最开始我也不打算把所有役都做了，毕竟条件是“有役和牌”，而且没有副露，所以好多可以叠加的役其实都不需要做，尤其是役满那些，大多都可以用其他役的条件满足。 &lt;br /&gt;
  在这期间，“平和”算是实现起来最麻烦的，主要是这个役需要根据和牌判定，必须是顺子的两面听牌才可以……虽然AI理解这些知识，但是写的时候经常出问题，像边张之类的经常误判……但是不实现又不行，毕竟这是可以nomi的牌型。 &lt;br /&gt;
  最终写的差不多了，感觉还不错，于是在GitHub Pages上&lt;a href=&quot;https://mabbs.github.io/riichi-mahjong-wordle/&quot;&gt;部署了一份&lt;/a&gt;，起名叫&lt;a href=&quot;https://github.com/Mabbs/riichi-mahjong-wordle&quot;&gt;立直麻将 Wordle&lt;/a&gt;。 &lt;br /&gt;
  最开始我以为这个小游戏的玩法是他们原创的，于是把生成的代码又丢给AI，让它评价一下创新性，结果AI说已经有开源了😂，有个叫做&lt;a href=&quot;https://github.com/xnuk/mahjong-handle&quot;&gt;Mahjong Handle&lt;/a&gt;的才是最早做这个玩法的……还好这代码不是我写的，要是我写了半天结果发现有现成的那就很无语了😆。 &lt;br /&gt;
  当然我让AI写的版本也有一些特色，Mahjong Handle那个和原版Wordle一样，每天只有一道题，而且这个题是从题库里抽的，我让AI Agent看了一下它的代码，似乎是用&lt;a href=&quot;https://github.com/MahjongRepository/phoenix-logs&quot;&gt;phoenix-logs&lt;/a&gt;这个脚本从天凤上下载的牌谱数据解析得到的题库，所以它的题目数量是有限的，而我的代码是完全随机生成的，所以结果是无限的。但AI似乎认为作为Wordle来说一天一题才是它的特色，无限就显得没意思了……既然如此，那干脆让它给我把两个游戏合并了，让它参考Mahjong Handle把一天一题的功能也给我加上，另外Mahjong Handle还有一个MPSZ快速输入法，也让AI给我合并进我的游戏里了。 &lt;br /&gt;
  这时候就体现了AI Agent的优势，如果直接用线上的AI把Mahjong Handle中的功能移植到我的代码，我还得完整描述要实现的功能，但是AI Agent能自己看人家仓库的代码，就知道要改什么东西了。 &lt;br /&gt;
  最终实现的效果也很不错，虽然我的代码没有题库，但是使用了当天日期作为随机种子，用mulberry32算法搞出了一天一题的功能，还挺有意思的。 &lt;br /&gt;
  另外在Mahjong Handle以及原版的Wordle中，还有一个困难模式，就是每次猜测必须使用之前的猜测条件，不过我觉得如果原模原样把这个功能抄过来似乎有点无聊，而且我发现玩这个东西一般直接一个国士+两个一气就基本上把要猜的牌找完了，有点简单，所以我想了一下，要不然限制一下提交的条件，提交的牌型必须包含目标的役种之类的……不过要是做这个功能就不能像之前那样只实现部分役种了，得把所有能用到的都实现一遍，还好这种事情肯定也不是我来做，肯定还得是让AI来😋。 &lt;br /&gt;
  实现的时候还不能让AI直接一口气全加上，还得一步一步的提示，而且每个牌型还得手动测一遍……最终虽然做的看起来似乎已经很完善了，不过逻辑的根本还是有点问题，AI设计役种计算的时候是按拆解后役种数量最多的情况来算，但事实上立直麻将算役种是按照高点法，要最终点数最大才行。虽然按照目前的方法绝大多数情况下都能正常工作，但偶尔也会遇到问题，比如像这副牌：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;🀈🀈🀈🀉🀉🀉🀊🀊🀊🀞🀟🀗🀗 🀝&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;按照目前的方法计算的结果是：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;断幺·平和·一杯口 = 3番30符&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;然而正确答案是：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;断幺·三暗刻 = 3番50符&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;但是符数的计算又很麻烦……不过反正提示和验牌都是同一套程序，题倒是也能做出来……所以就懒得修了🤣。当然说到底还是因为AI没有用现有的计算库，像Mahjong Handle用的是现成的&lt;a href=&quot;https://github.com/takayama-lily/riichi&quot;&gt;Riichi&lt;/a&gt;库，制作起来比我这个就简单多了。&lt;/p&gt;

&lt;h1 id=&quot;为游戏添加字体&quot;&gt;为游戏添加字体&lt;/h1&gt;
&lt;p&gt;在我制作“立直麻将 Wordle”这个小游戏的时候，我让AI使用的是Unicode中的麻将字符，毕竟有现成的字符总比去找牌的图片要方便，而且实现起来感觉更简单一些。不过用字符确实也会遇到一些问题，我不知道为什么在macOS和Android上其他牌都是黑白的，唯有🀄️是彩色的😅，Windows上使用的Segoe UI Emoji倒都是彩色的，但是牌很丑，像是小学生设计的……虽然不影响玩，但是看着很难受，至少牌的颜色统一一下也好吧？虽然也可以搞出黑白的🀄︎，但如果用这个，在Windows上又成了只有🀄️是黑白的😅。虽然Mahjong Handle是直接用了SVG格式的牌图片，但我觉得应该有现成的麻将字体，像这种问题应该有现成的解决方法才对吧？ &lt;br /&gt;
  于是我直接问AI有没有这样的字体，AI搜了一下看起来还真有，有个叫做&lt;a href=&quot;https://github.com/kfarwell/Mahjong-Colored&quot;&gt;Mahjong Colored&lt;/a&gt;就是基于那些SVG做的OpenType-SVG字体，我本来以为问题就会这样解决，然而当我应用了字体之后发现牌的位置变成了白色的方块，貌似只有Firefox才支持这种字体……然而现在谁还用Firefox啊？ &lt;br /&gt;
  那遇到这种问题怎么办？当然还是让AI解决，它说要在Safari和Chrome上使用彩色字体，用COLR或者SBIX格式比较好，COLR格式的兼容性最好，三个浏览器都能用，而SBIX的话Firefox没法用。不过Firefox既然基本上没人用了，我倒是不太在乎它的兼容性，但我肯定还是更希望能用兼容性更好的方案，于是我把OpenType-SVG字体转换为COLR的工作交给了AI Agent。 &lt;br /&gt;
  结果它整了半天也没整出来，貌似是因为SVG的特效太多，没办法转换为COLR格式，于是它选择了SBIX方案，不过COLR存储的是矢量图，而SBIX存储的是位图，大小要大一些，而且放大还会有锯齿。只是目前实在是没办法了，只能用这种格式。 &lt;br /&gt;
  最终我把它转换后的字体存了一份到&lt;a href=&quot;https://github.com/Mabbs/Mahjong-Colored-SBIX&quot;&gt;仓库&lt;/a&gt;，但是看着它1MiB多的大小以及不太好的浏览器兼容性，再考虑到它还是位图，实在是不太想在我的游戏里应用它…… &lt;br /&gt;
  结果后来我又搜了一下，原来是有现成的麻将字体啊，有个叫做&lt;a href=&quot;https://github.com/rutopio/mahjong-font&quot;&gt;Mahjong Font&lt;/a&gt;的项目用的就是COLR格式，兼容性非常好，而且大小只有80KiB，算是最完美的解决方案了。垃圾AI给我推荐的Mahjong Colored真的是……浪费了不少时间，看来有的时候自己调查要比问AI好一些吧。&lt;/p&gt;

&lt;h1 id=&quot;感想&quot;&gt;感想&lt;/h1&gt;
&lt;p&gt;总的来看，目前Vibe Coding的体验确实是相当不错，只是问题也非常明显，完全让AI生成的代码要么实现的不优雅，要么就会出BUG，如果完全不懂要写的东西，纯粹由AI生成，那出问题的概率就非常高了。不过要是看不出来错误，完全被AI忽悠过去了，也许这时候的Vibe Coding在另一方面算是完美吧😂？至少对用的人来说也算是解决了问题呢。&lt;/p&gt;</content><author><name>mayx</name></author><category term="Vibe Coding" /><category term="AI" /><category term="PJAX" /><category term="麻将" /><summary type="html">All in AI!</summary></entry><entry><title type="html">如何节约游戏占用的硬盘空间？</title><link href="/2026/06/01/dedupe.html" rel="alternate" type="text/html" title="如何节约游戏占用的硬盘空间？" /><published>2026-06-01T00:00:00+08:00</published><updated>2026-06-01T00:00:00+08:00</updated><id>/2026/06/01/dedupe</id><content type="html" xml:base="/2026/06/01/dedupe.html">&lt;p&gt;浪费硬盘空间是可耻的！&lt;!--more--&gt;&lt;/p&gt;

&lt;h1 id=&quot;起因&quot;&gt;起因&lt;/h1&gt;
&lt;p&gt;在几年前，我写过一篇在&lt;a href=&quot;/2023/10/21/game.html&quot;&gt;MacBook上玩游戏&lt;/a&gt;的文章，在那之后，我已经在我的Mac上下载了几十部游戏。只不过有个问题……我的Mac只有256GiB的硬盘存储空间，下载一堆游戏会让我的硬盘空间不够用，但是又不太想删，所以我该怎么尽可能让游戏占用更少的空间呢？ &lt;br /&gt;
  首先为了能在Mac上尽可能流畅地玩，我玩的游戏大多都是用跨平台能力很强的引擎编写的游戏，比如&lt;a href=&quot;https://github.com/renpy/renpy&quot;&gt;Ren’Py&lt;/a&gt;、RPG制作大师、Godot之类的，而像RPG制作大师这种引擎制作的游戏还有一个特点，开发者一般都会使用引擎自带的素材进行开发，有时候还会用不少第三方的罐头素材之类的（实际上甚至还有好多AVG为了蹭这些引擎的公用素材刻意用它们），所以这几十个游戏里应该有非常多的重复素材，如果能想办法把它们去个重，应该能节省相当多的空间吧……&lt;/p&gt;

&lt;h1 id=&quot;去重的方法&quot;&gt;去重的方法&lt;/h1&gt;
&lt;p&gt;如果想要对文件进行去重，我搜了一下，有个叫做&lt;a href=&quot;https://codeberg.org/jbruchon/jdupes&quot;&gt;jdupes&lt;/a&gt;的工具就很不错，它支持多种去重方式，比如使用硬链接，或者用一些文件系统的写时复制特性。不过如果用写时复制特性，jdupes在第二次执行的时候会认为去重后的文件还是单独的文件，就会重复去重了，而且最终也不好统计，反正对我玩的游戏来说，要去重的都是游戏素材，不存在后续修改的可能性，所以我打算全部用硬链接。 &lt;br /&gt;
  所以最终要执行的命令也非常简单，直接一句&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jdupes -r -L Game&lt;/code&gt;就可以了，这样以后每次下载了新的游戏之后重复执行这个操作，就可以将游戏中和其他游戏里有的素材去重了。 &lt;br /&gt;
  不过实际上很多游戏并不能直接用这种方式去重，因为它们的资源文件有些是打包成单个文件，有些进行了简单的加密，导致即使是相同的素材，文件也并不相同，所以我必须让所有的资源以单独原始的形态出现。对于不同的引擎也有不同的处理方式，所以接下来我需要对它们进行一些研究。&lt;/p&gt;

&lt;h1 id=&quot;不同引擎的处理方式&quot;&gt;不同引擎的处理方式&lt;/h1&gt;
&lt;h2 id=&quot;rpg制作大师mvmz&quot;&gt;RPG制作大师MV/MZ&lt;/h2&gt;
&lt;p&gt;对于RPG制作大师MV/MZ开发的游戏来说，解密很简单，比较知名的是一个叫做&lt;a href=&quot;https://gitlab.com/Petschko/RPG-Maker-MV-Decrypter&quot;&gt;RPG-Maker-MV-Decrypter&lt;/a&gt;的工具，它可以在浏览器中进行解密，但一个游戏的资源文件非常多……要是全上传给浏览器实在是太麻烦了……后来我又搜了一下，有一个用C#写的叫&lt;a href=&quot;https://github.com/uuksu/RPGMakerDecrypter&quot;&gt;RPG Maker Decrypter&lt;/a&gt;工具也很不错，它作为命令行工具比在浏览器中执行简单多了，而且还能只把资源文件单独提出来，这样就可以剔除掉游戏自带的浏览器文件。不过他这个仓库的代码有个问题，它在选择文件的时候似乎会区分大小写，文件夹名中含有大写字母的似乎会被剔除……这样不太符合我的要求啊，当然我不会C#，于是我用AI改了一下，还给他提了个&lt;a href=&quot;https://github.com/uuksu/RPGMakerDecrypter/pull/28&quot;&gt;PR&lt;/a&gt;，不过这家伙看起来似乎不太喜欢AI写的代码，看起来不打算合我的PR😅。不过无所谓了，反正我也是自用，他爱合不合吧。 &lt;br /&gt;
  这个工具的用法也非常简单，一句&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RPGMakerDecrypter-cli [input] -p -o [output]&lt;/code&gt;就处理好了，处理完之后只需要把&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data/System.json&lt;/code&gt;中的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hasEncryptedImages&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hasEncryptedAudio&lt;/code&gt;设置为false就可以正常识别，以后在Mac中只要在游戏路径下执行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;python3 -m http.server&lt;/code&gt;就可以在浏览器中游玩了。 &lt;br /&gt;
  在这个过程中，我还发现有一些游戏喜欢把原画文件直接放到游戏里面，一张图片好几M，但RPG制作大师的引擎在渲染的时候根本不会渲染出那么高的分辨率，结果毫无意义地浪费一大堆存储空间，而且因为图片是加密的，对大多数人来说也没有收藏价值。所以在解密完之后我就想干脆把这些图片全部有损压缩一遍，估计能节省不少存储空间，于是让AI写了个简单的压缩脚本处理了一下：&lt;/p&gt;
&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;
图片压缩脚本（多进程版本）
将 pictures.orig 文件夹中的图片使用 WebP 格式进行高效压缩，
保持分辨率不变，肉眼看不出差异，压缩后的图片保存到 pictures 文件夹。

使用方法:
    python3 compress_images.py

压缩策略:
    - 保持原始分辨率不变
    - 使用 WebP 格式（有损压缩，高质量）
    - 质量设置为 85，在保持视觉质量的同时显著减小文件大小
    - 文件名和后缀保持不变
    - 多进程并行处理
    - 处理失败时自动复制原文件
&quot;&quot;&quot;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;os&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;shutil&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;PIL&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Image&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;pathlib&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Path&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;multiprocessing&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Pool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cpu_count&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;functools&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;partial&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 配置路径
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SOURCE_DIR&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;pictures.orig&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;OUTPUT_DIR&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;pictures&quot;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# WebP 质量设置 (0-100，数值越高质量越好，文件也越大)
# 85 是一个很好的平衡点，肉眼几乎看不出差异
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WEBP_QUALITY&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;85&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 对于带有透明通道的图片，可以设置不同的质量
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WEBP_QUALITY_WITH_ALPHA&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;80&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# 并行进程数，默认为 CPU 核心数
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NUM_WORKERS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cpu_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;compress_single_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;img_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;tuple&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;tuple&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;
    压缩单个图片文件（用于多进程）
    
    Args:
        img_file: (源文件路径, 输出文件路径, 输出目录) 元组
        
    Returns:
        (文件名, 是否成功, 原始大小, 压缩后大小) 元组
    &quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;source_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;output_path_str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;output_dir&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;img_file&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;source_path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;source_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;output_path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;output_path_str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    
    &lt;span class=&quot;n&quot;&gt;original_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;source_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;st_size&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;img&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;source_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        
        &lt;span class=&quot;c1&quot;&gt;# 检查是否有透明通道
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;has_alpha&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;img&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mode&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;RGBA&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;LA&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;PA&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;img&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;P&apos;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;transparency&apos;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;img&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        
        &lt;span class=&quot;c1&quot;&gt;# 确定使用的质量
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;quality&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WEBP_QUALITY_WITH_ALPHA&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;has_alpha&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WEBP_QUALITY&lt;/span&gt;
        
        &lt;span class=&quot;c1&quot;&gt;# 保存为 WebP 格式，但使用原始的文件扩展名
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;img&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;output_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;WEBP&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;quality&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;quality&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# 压缩方法 0-6，6 是最慢但压缩率最高的
&lt;/span&gt;        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        
        &lt;span class=&quot;n&quot;&gt;compressed_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;output_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;st_size&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;source_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;original_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;compressed_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        
    &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# 处理失败时，复制原文件到输出目录
&lt;/span&gt;        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;shutil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;copy2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;source_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;output_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;compressed_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;output_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;st_size&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;source_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;original_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;compressed_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;copy_error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;source_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;original_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;source_dir&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SOURCE_DIR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;output_dir&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;OUTPUT_DIR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    
    &lt;span class=&quot;c1&quot;&gt;# 检查源目录是否存在
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;source_dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exists&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;错误: 源目录 &apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SOURCE_DIR&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos; 不存在&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;
    
    &lt;span class=&quot;c1&quot;&gt;# 创建输出目录
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;output_dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mkdir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exist_ok&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    
    &lt;span class=&quot;c1&quot;&gt;# 获取所有图片文件（支持多种格式）
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;image_extensions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;*.png&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;*.jpg&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;*.jpeg&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;*.bmp&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;*.gif&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;*.tiff&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;*.webp&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;image_files&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ext&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;image_extensions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;image_files&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;extend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;source_dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;glob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;image_files&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sorted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;image_files&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# 去重并排序
&lt;/span&gt;    
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;image_files&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;在 &apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SOURCE_DIR&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos; 中没有找到图片文件&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;
    
    &lt;span class=&quot;c1&quot;&gt;# 构建任务列表
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;tasks&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;img_file&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;image_files&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;output_path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;output_dir&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;img_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# 保持原文件名和后缀
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;img_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;output_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;output_dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;找到 &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; 个图片文件&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;源目录: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SOURCE_DIR&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;输出目录: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;OUTPUT_DIR&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;WebP 质量设置: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WEBP_QUALITY&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;并行进程数: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NUM_WORKERS&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;-&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;70&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    
    &lt;span class=&quot;c1&quot;&gt;# 使用多进程池处理图片
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;success_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;fail_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;total_original&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;total_compressed&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Pool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;processes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NUM_WORKERS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;original_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;compressed_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;enumerate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;imap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;compress_single_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;total_original&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;original_size&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;total_compressed&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;compressed_size&lt;/span&gt;
            
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;success_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;marker&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;✓&quot;&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;reduction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;compressed_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;original_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;original_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;status_msg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reduction&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;%&quot;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;fail_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;marker&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;✗&quot;&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;status_msg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;复制原文件&quot;&lt;/span&gt;
            
            &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;[&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;] &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;marker&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;original_size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1024&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;8.1&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;KB -&amp;gt; &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;compressed_size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1024&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;8.1&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;KB (&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;status_msg&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    
    &lt;span class=&quot;c1&quot;&gt;# 输出总结
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;-&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;70&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;total_reduction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_compressed&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_original&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_original&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;压缩完成!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;  成功处理: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;success_count&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; 个文件&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fail_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;  失败(已复制原文件): &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fail_count&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; 个文件&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;  原始总大小: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;total_original&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1024&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1024&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; MB (&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;total_original&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1024&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; KB)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;  压缩后大小: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;total_compressed&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1024&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1024&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; MB (&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;total_compressed&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1024&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; KB)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;  总压缩率: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;total_reduction&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;%&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;  节省空间: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;total_original&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_compressed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1024&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1024&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; MB&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__name__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;__main__&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;最终压缩完之后我把原图上传到了&lt;a href=&quot;https://e-hentai.org/g/3901673/426a7a17ba/&quot;&gt;EH画廊&lt;/a&gt;中，本地只留压缩后的图片，大小从原来的2GiB多下降到了300多MiB，可以说效果相当显著了。 &lt;br /&gt;
  除此之外还有一些游戏使用了Ogg FLAC背景音乐，这种音乐不仅占用磁盘空间很大，而且我在Safari上玩的时候浏览器根本没法解析（Chrome应该可以）。虽然我听音乐是会考虑&lt;a href=&quot;/2025/03/22/hifi.html&quot;&gt;HiFi&lt;/a&gt;，但玩游戏就没必要了吧……所以像这种音乐，就得用一句：&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ffmpeg &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; input.flac.ogg &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt;:a vorbis &lt;span class=&quot;nt&quot;&gt;-strict&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-2&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-q&lt;/span&gt;:a 10 output.ogg
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;转换为正常有损的Ogg音乐了。&lt;/p&gt;
&lt;h2 id=&quot;rpg制作大师xpvxva&quot;&gt;RPG制作大师XP/VX/VA&lt;/h2&gt;
&lt;p&gt;对于RPG制作大师XP/VX/VA引擎开发的游戏来说，它们都是基于用Ruby语言开发的RGSS编写的，作为脚本来说，倒是有跨平台的条件，但因为官方并没有做跨平台，所以不能直接在Mac上运行。不过有一款叫做&lt;a href=&quot;https://github.com/mkxp-z/mkxp-z&quot;&gt;mkxp-z&lt;/a&gt;的工具允许跨平台运行使用RPG制作大师XP/VX/VA制作的游戏，因此这类游戏我也收集了一些。 &lt;br /&gt;
  这些游戏的资源通常会进行简单的混淆加密，一般会打包成单个RGSSAD文件，这个解包也很简单，用刚刚的RPG Maker Decrypter就可以。不过这种游戏还有个特点，有些游戏需要使用&lt;a href=&quot;https://www.rpgmakerweb.com/run-time-package&quot;&gt;RTP&lt;/a&gt;才能运行，它这个RTP其实就是RPG制作大师自带的素材包，当时设计出来估计也是想着用来节约硬盘空间吧，就是不知道为什么到后来的MV/MZ却取消了这种方式……虽然mkxp-z是支持通过配置文件引入RTP的，但既然我已经选择了硬链接的方式，就没必要单独搞RTP了，我选择把RTP直接和游戏合并，然后让jdupes直接去重就好了，这样相比于RTP的方式还有一些好处就是XP/VX/VA可能有一些和MV/MZ使用相同的素材，这部分也可以不用占用重复的空间了。&lt;/p&gt;
&lt;h2 id=&quot;renpy&quot;&gt;Ren’Py&lt;/h2&gt;
&lt;p&gt;对于Ren’Py来说，因为这个引擎并没有自带的公共资源，所以重复素材的问题并不是很大。不过在我之前对&lt;a href=&quot;/2024/01/20/renpy.html&quot;&gt;Ren’Py的探索&lt;/a&gt;中提到过，我玩的一些游戏是系列游戏，这种系列游戏有非常多的素材复用，但显然开发者并不会为了节约玩家硬盘空间而共享这部分资源，而且Ren’Py游戏也都是打包成单个文件的，所以接下来我们依然得要解包才能进行去重处理。 &lt;br /&gt;
  Ren’Py使用的rpa文件解包起来依然很简单，有一款现成的工具&lt;a href=&quot;https://github.com/Lattyware/unrpa&quot;&gt;unrpa&lt;/a&gt;可以直接解包，用pip就能安装。不知道为什么这些引擎总是喜欢把资源文件都打成一个包，明明很容易就能解包……难道是为了性能吗？ &lt;br /&gt;
  不过也正是因为Ren’Py的公共资源不多，如果玩的不是系列游戏，就没有解包的必要了，解包之后一堆小文件有可能会比整个rpa文件更大，毕竟文件系统存在“簇”，有可能会消耗没对齐的空间。&lt;/p&gt;

&lt;h1 id=&quot;验证结果&quot;&gt;验证结果&lt;/h1&gt;
&lt;p&gt;最终进行完上述操作，可以通过执行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;du -sh&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;du -shl&lt;/code&gt;进行对比来验证节约的硬盘空间，我在这次游戏的瘦身中节约了：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;~ % du -sh Game                                
 33G	Game
~ % du -shl Game
 47G	Game
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;看起来还是相当可观啊……尤其是在当下硬盘价格大涨的情况下，如果很多人能通过这些方式来节约硬盘空间，就能减少对硬盘容量的需求吧……不过说到底其实也都是网上能下到的资源，也许玩完之后就删掉才是最好的节约硬盘的方式吧😂。&lt;/p&gt;

&lt;p&gt;&lt;input name=&quot;live2dBGM&quot; value=&quot;https://music.163.com/song/media/outer/url?id=1968116350.mp3&quot; type=&quot;hidden&quot; /&gt;&lt;/p&gt;</content><author><name>mayx</name></author><category term="dedupe" /><category term="RPG制作大师" /><category term="游戏" /><summary type="html">浪费硬盘空间是可耻的！</summary></entry><entry><title type="html">虚拟局域网的组网探索记录</title><link href="/2026/05/01/virtual-net.html" rel="alternate" type="text/html" title="虚拟局域网的组网探索记录" /><published>2026-05-01T00:00:00+08:00</published><updated>2026-05-01T00:00:00+08:00</updated><id>/2026/05/01/virtual-net</id><content type="html" xml:base="/2026/05/01/virtual-net.html">&lt;p&gt;异地组网，有多少种选择？&lt;!--more--&gt;&lt;/p&gt;

&lt;h1 id=&quot;起因&quot;&gt;起因&lt;/h1&gt;
&lt;p&gt;最近我有一些放置在许多不同地方的机器，有一些东西需要让它们之间能够相互访问。虽然我很久以前写过一篇使用&lt;a href=&quot;/2021/05/07/ssh.html&quot;&gt;SSH进行互联&lt;/a&gt;的文章，但这样做每个服务都需要单独配置，也不方便管理。所以为了能让机器之间能够轻松通信，我打算组建一个虚拟局域网，让它们像在同一交换机下一样。不过这种组网的工具非常多，我应该选哪个比较好呢？&lt;/p&gt;

&lt;h1 id=&quot;不同组网工具的体验&quot;&gt;不同组网工具的体验&lt;/h1&gt;
&lt;h2 id=&quot;n2n&quot;&gt;n2n&lt;/h2&gt;
&lt;p&gt;以前我用过一款用C写的叫做&lt;a href=&quot;https://github.com/ntop/n2n&quot;&gt;n2n&lt;/a&gt;的工具，它可以很轻松地组建一个P2P的二层虚拟网络，而且生态也不错，手机、电脑、路由器、服务器上都有可以用的客户端。使用起来非常简单，它的中继和穿透服务程序叫做Supernode，无需太多的配置，只要在有公网的服务器安装并使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-p&lt;/code&gt;指定一个端口就可以启动。而客户端配置也非常简单，用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-l&lt;/code&gt;配置好Supernode的地址，然后让想要在同一个网络的机器使用相同的任意&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-k&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-c&lt;/code&gt;就可以成功组网，可以说算是非常好用了。 &lt;br /&gt;
  唯一的问题就是它这个项目看起来似乎已经停止更新了……虽然大多数情况下用起来没问题，但是有时候还是会出现组网不太可靠的情况。如果两个机器都不经过NAT，可以通过公网IP连接，它的可靠性还可以。但如果是两个NAT后的机器之间，有时候会存在莫名掉线的情况，也许是因为穿透导致的不可靠？总之遇到这种情况之后重启又能正常工作，说明是软件本身的问题，但它停更了……所以对我来说它的可靠性不太够。（其实它还有个叫做&lt;a href=&quot;https://github.com/n42n/n3n&quot;&gt;n3n&lt;/a&gt;的继任者，不过知名度不高，所以生态也不太行）&lt;/p&gt;
&lt;h2 id=&quot;wireguard&quot;&gt;WireGuard&lt;/h2&gt;
&lt;p&gt;其实在这之后我本来是打算用L2TP/IPSec进行组网的，但看了一下貌似配置有点复杂，而且不够现代，现在想要组网貌似大多都推荐&lt;a href=&quot;https://git.zx2c4.com/wireguard-linux/&quot;&gt;WireGuard&lt;/a&gt;作为更现代的选择。只不过它和n2n相比来说是三层的虚拟网络，如果需要发送非TCP/IP协议的特别包，可能就用不了它吧，当然对我来说没有这种需求。它用起来也非常简单，不过正常情况下它设计是为了点对点传输，而且没有自带的NAT穿透功能，所以如果想要实现组网，就得搭一个星形网络，让互联网上的服务器作为虚拟的交换机，这个做起来倒也不复杂。首先，每个节点需要生成一个公私钥对作为身份证明，在安装好WireGuard之后执行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wg genkey&lt;/code&gt;就能生成私钥。作为交换机的节点需要在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/wireguard/wg0.conf&lt;/code&gt;中写一个这样的配置：&lt;/p&gt;
&lt;div class=&quot;language-conf highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[&lt;span class=&quot;n&quot;&gt;Interface&lt;/span&gt;]
&lt;span class=&quot;n&quot;&gt;PrivateKey&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;xxx&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Address&lt;/span&gt; = &lt;span class=&quot;m&quot;&gt;192&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;168&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;24&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ListenPort&lt;/span&gt; = &lt;span class=&quot;m&quot;&gt;51820&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;PostUp&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;iptables&lt;/span&gt; -&lt;span class=&quot;n&quot;&gt;A&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FORWARD&lt;/span&gt; -&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wg0&lt;/span&gt; -&lt;span class=&quot;n&quot;&gt;o&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wg0&lt;/span&gt; -&lt;span class=&quot;n&quot;&gt;j&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ACCEPT&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;PostDown&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;iptables&lt;/span&gt; -&lt;span class=&quot;n&quot;&gt;D&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FORWARD&lt;/span&gt; -&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wg0&lt;/span&gt; -&lt;span class=&quot;n&quot;&gt;o&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wg0&lt;/span&gt; -&lt;span class=&quot;n&quot;&gt;j&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ACCEPT&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 机器1
&lt;/span&gt;[&lt;span class=&quot;n&quot;&gt;Peer&lt;/span&gt;]
&lt;span class=&quot;n&quot;&gt;PublicKey&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;xxx&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;AllowedIPs&lt;/span&gt; = &lt;span class=&quot;m&quot;&gt;192&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;168&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;32&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 机器2
&lt;/span&gt;[&lt;span class=&quot;n&quot;&gt;Peer&lt;/span&gt;]
&lt;span class=&quot;n&quot;&gt;PublicKey&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;xxx&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;AllowedIPs&lt;/span&gt; = &lt;span class=&quot;m&quot;&gt;192&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;168&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;3&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;32&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;其中PrivateKey填写交换机自己的私钥，而作为使用者的Peer中的PublicKey可以用对应节点的私钥执行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;echo xxx | wg pubkey&lt;/code&gt;这个命令查看，然后每个Peer需要像这样配置：&lt;/p&gt;
&lt;div class=&quot;language-conf highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[&lt;span class=&quot;n&quot;&gt;Interface&lt;/span&gt;]
&lt;span class=&quot;n&quot;&gt;PrivateKey&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;xxx&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Address&lt;/span&gt; = &lt;span class=&quot;m&quot;&gt;192&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;168&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;24&lt;/span&gt;

[&lt;span class=&quot;n&quot;&gt;Peer&lt;/span&gt;]
&lt;span class=&quot;n&quot;&gt;PublicKey&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;xxx&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# 交换机节点的公钥
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Endpoint&lt;/span&gt; = &lt;span class=&quot;n&quot;&gt;xxx&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;xxx&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;xxx&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;xxx&lt;/span&gt;:&lt;span class=&quot;m&quot;&gt;51820&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# 交换机节点的地址
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AllowedIPs&lt;/span&gt; = &lt;span class=&quot;m&quot;&gt;192&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;168&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;24&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;PersistentKeepalive&lt;/span&gt; = &lt;span class=&quot;m&quot;&gt;25&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;最后全都配置好之后所有节点使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;systemctl enable --now wg-quick@wg0&lt;/code&gt;启动就可以了，启动之后每个节点可以执行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wg&lt;/code&gt;查看当前的连接状态。 &lt;br /&gt;
  当然这是在Linux上，至于其他系统大多都有GUI配置，填起来更简单。它的生态也非常好，基本上常见的操作系统都支持，具体可以在&lt;a href=&quot;https://www.wireguard.com/install/&quot;&gt;官网&lt;/a&gt;查看支持的系统和安装方法。不过由于它在Linux中优先使用内核模块，导致我在一些比较小众的环境中也是遇到了各种特别的问题。&lt;/p&gt;
&lt;h3 id=&quot;在红米ax3000中遇到的问题&quot;&gt;在红米AX3000中遇到的问题&lt;/h3&gt;
&lt;p&gt;我在这个网络中有几个安装了OpenWrt的路由器，在这其中使用联发科芯片的路由器基本上都没什么问题，官网能轻松下载到固件，也能很轻松地在软件包中找到WireGuard并安装，但我还有一台使用高通芯片的红米AX3000，似乎因为高通对资料管控得很严格，导致它没有官网的固件，最终我在GitHub上找了一个其他人自己编译的&lt;a href=&quot;https://github.com/hzyitc/openwrt-redmi-ax3000/&quot;&gt;固件&lt;/a&gt;。虽然它整起来有点麻烦，不过倒也能用，但是在我尝试安装WireGuard的时候遇到了麻烦…… &lt;br /&gt;
  它的软件包里有WireGuard，也能找到对应的内核模块安装包，但安装完之后没法启动……随后我看了一下它下载的&lt;a href=&quot;https://github.com/hzyitc/openwrt-redmi-ax3000/blob/gh-pages/ipq50xx-qsdk-kernel-5.4-openwrt-21.02-qsdk-11.5.05.841.1029/ci-20240727-173350-ab1f9ffa/kmod-wireguard_5.4-qsdk-11.5.0.5-1_arm_cortex-a7_neon-vfpv4.ipk&quot;&gt;安装包&lt;/a&gt;，结果发现是空的😰，它这个固件的内核模块可能是在编译的时候遇到了一些问题。至于让我自己编译这个内核模块，难度似乎有点高了……那怎么办呢？要知道Linux的内核模块都是和内核挂钩的，没办法随便找一个别的模块使用。还好WireGuard倒也不止有内核模块，也有一些在用户空间中的实现，比如&lt;a href=&quot;https://git.zx2c4.com/wireguard-go&quot;&gt;wireguard-go&lt;/a&gt;和&lt;a href=&quot;https://git.zx2c4.com/wireguard-rs&quot;&gt;wireguard-rs&lt;/a&gt;。只是官方似乎非常不推荐在Linux上使用它们，所以没有提供预编译的版本。不过遇到这种问题的人也许是比较多，所以有人做了在&lt;a href=&quot;https://github.com/seud0nym/openwrt-wireguard-go&quot;&gt;OpenWrt上使用的wireguard-go&lt;/a&gt;，安装好之后效果和使用内核模块的感觉基本上没什么区别，最终也能连通，唯一的区别就是在执行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wg&lt;/code&gt;的时候，会显示“Interface: wg0 (userspace)”罢了。从效率上来说虽然肯定没有内核模块那么高，但它其实也用了“Tun”模块，理论上和使用“Tap”模块的n2n应该差不多吧。&lt;/p&gt;
&lt;h3 id=&quot;在openeuler中遇到的问题&quot;&gt;在openEuler中遇到的问题&lt;/h3&gt;
&lt;p&gt;在我使用的节点中，还有一台安装了openEuler 22.03 LTS操作系统的服务器，虽然openEuler和CentOS可以说基本上没什么区别，但毕竟它的内核是openEuler自己编译的，所以没办法直接使用CentOS的内核模块。并且openEuler的源中也完全没有提供和WireGuard相关的包，所以想要在openEuler上安装WireGuard还是有些挑战（当然如果觉得麻烦，它们倒是有一个兼容WireGuard的客户端&lt;a href=&quot;https://eur.openeuler.openatom.cn/coprs/nucleo/tunsafe/&quot;&gt;TunSafe&lt;/a&gt;可以凑活用一下）。 &lt;br /&gt;
  后来我试了一下在这上面安装wireguard-tools倒是可以直接用&lt;a href=&quot;https://mirrors.tuna.tsinghua.edu.cn/epel/8/Everything/x86_64/Packages/w/wireguard-tools-1.0.20210914-1.el8.x86_64.rpm&quot;&gt;CentOS 8EPEL源中的包&lt;/a&gt;，但openEuler的内核在编译的时候故意没有包含WireGuard内核模块……这该怎么办呢？用wireguard-go吗？虽然这样可以很简单地解决，但感觉这样就是认输了😂。后来我搜了一下，找到了一篇&lt;a href=&quot;https://dingle.site/archives/wei-openeulertian-jia-wireguardmo-kuai&quot;&gt;在openEuler安装WireGuard内核模块&lt;/a&gt;的文章，方法大致如下：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;首先安装编译环境和源代码。
    &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;yum &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;elfutils-libelf-devel kernel-devel pkgconfig &lt;span class=&quot;s2&quot;&gt;&quot;@Development Tools&quot;&lt;/span&gt;
yum &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;kernel-headers.x86_64 pkg-config ncurses-devel openssl-devel dwarves
yum &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;kernel-source.x86_64
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;然后进行编译配置，内核源码一般会安装到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/src/&lt;/code&gt;下，找到之后在里面执行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make menuconfig&lt;/code&gt;，然后勾选“Device Drivers -&amp;gt; Network device support -&amp;gt; Wireguard secure network tunnel”并保存。&lt;/li&gt;
  &lt;li&gt;最后执行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make&lt;/code&gt;开始编译，为了加速可以用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-j&lt;/code&gt;参数加上CPU的核心数进行并行编译，当时编译就花掉了一整天😂，理论上应该可以只编译WireGuard和它依赖的几个模块，不过我不太清楚怎么做，还是费点时间按照文中说的做吧。&lt;/li&gt;
  &lt;li&gt;执行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make modules_install&lt;/code&gt;将编译好的结果安装到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/lib/modules/5.10.0&lt;/code&gt;。
  不过系统似乎不会去这个路径下找内核模块，所以还得把这里面的kernel文件夹复制到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/lib/modules/$(uname -r)&lt;/code&gt;下，然后执行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;depmod -a&lt;/code&gt;更新模块依赖。&lt;/li&gt;
  &lt;li&gt;最后执行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;modprobe wireguard&lt;/code&gt;验证模块是否能正常加载，如果没有报错并且可以在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lsmod | grep wireguard&lt;/code&gt;中看到，就说明安装成功了，剩余的步骤和其他Linux系统一样。&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;wireguard的控制平面&quot;&gt;WireGuard的控制平面&lt;/h3&gt;
&lt;p&gt;虽然WireGuard本身配置很简单，但每加一个节点还得在交换机节点上修改一下配置文件，稍微有些麻烦，所以有人开发了一些控制平面，让它可以被更规范地管理，比如&lt;a href=&quot;https://github.com/gravitl/netmaker&quot;&gt;Netmaker&lt;/a&gt;和&lt;a href=&quot;https://github.com/juanfont/headscale&quot;&gt;Headscale&lt;/a&gt;。而Headscale主要是为Tailscale客户端开发的开源服务器端，因此功能会局限于Tailscale提供的功能。所以如果没有用过Tailscale，可以优先考虑Netmaker。 &lt;br /&gt;
  这两个控制平面支持的功能相当丰富，而且它们还支持让WireGuard进行NAT穿透，自动组建Mesh网络，不像我一堆在NAT后的设备还要直接使用WireGuard就只能搭成星形网络。只不过对我来说，我也用不到那么多企业级功能，这个服务端配置起来也有点麻烦，而且我也没有很多节点需要动态增减，我的云端服务器带宽也足够使用，所以就没有用这些东西了😆。&lt;/p&gt;
&lt;h2 id=&quot;其他的组网工具&quot;&gt;其他的组网工具&lt;/h2&gt;
&lt;p&gt;除了WireGuard之外，还有很多其他的组网工具，比如&lt;a href=&quot;https://github.com/vnt-dev/vnt&quot;&gt;VNT&lt;/a&gt;和&lt;a href=&quot;https://github.com/EasyTier/Easytier&quot;&gt;EasyTier&lt;/a&gt;，这俩用起来也非常简单，只需要加几个参数就能组网，和n2n一样。不过功能相比于n2n来说要强大不少，也支持NAT穿透，而且还都兼容WireGuard协议，另外不像WireGuard强制使用UDP传输，这两个还能用TCP和WebSocket，在特殊网络环境下应该比直接用WireGuard更好。另外它们都是Rust编写的，也许会更安全😋？可惜我已经配好WireGuard之后懒得再改了，如果以后有机会，可以尝试一下。&lt;/p&gt;

&lt;h1 id=&quot;总结&quot;&gt;总结&lt;/h1&gt;
&lt;p&gt;现在如果想要异地搭建虚拟局域网，还是有相当多的选择，而且无论是性能还是配置难度，都比以前好了不少。看来这种需求还是相当多啊，也正是因为有这些需求，所以才会出现这么多的方案可以用吧……总之我最后还是选择了纯WireGuard方案，主要还是简单够用，可靠性也不错，而且折腾了这么多再换也不太合适吧🤣。&lt;/p&gt;</content><author><name>mayx</name></author><category term="虚拟网络" /><category term="异地组网" /><category term="WireGuard" /><summary type="html">异地组网，有多少种选择？</summary></entry><entry><title type="html">关于AI个人助理的探索</title><link href="/2026/04/14/ai-agent.html" rel="alternate" type="text/html" title="关于AI个人助理的探索" /><published>2026-04-14T00:00:00+08:00</published><updated>2026-04-14T00:00:00+08:00</updated><id>/2026/04/14/ai-agent</id><content type="html" xml:base="/2026/04/14/ai-agent.html">&lt;p&gt;给AI添加手脚能有多少种方法？&lt;!--more--&gt;&lt;/p&gt;

&lt;h1 id=&quot;起因&quot;&gt;起因&lt;/h1&gt;
&lt;p&gt;最近像&lt;a href=&quot;https://github.com/openclaw/openclaw&quot;&gt;OpenClaw&lt;/a&gt;这样被叫做“AI个人助理”的Agent越来越火了，当然这种东西在我看来依然是新瓶装旧酒，整来整去还就是和AutoGPT一样。这种东西在当时GPT-3.5的时候就有了，现在只不过是增加了聊天软件交互的渠道便突然大火，和当年的Manus一样……实在是无法理解。 &lt;br /&gt;
  一年前我用过Devin.ai这个云端的Agent编写过&lt;a href=&quot;/2025/04/08/feed.html&quot;&gt;用JS解析订阅源&lt;/a&gt;的脚本，体验还算不错，既然现在已经过了一年，那就让我看看现在又有了什么样的发展吧。&lt;/p&gt;

&lt;h1 id=&quot;使用ai个人助理&quot;&gt;使用AI个人助理&lt;/h1&gt;
&lt;h2 id=&quot;体验原生openclaw&quot;&gt;体验原生OpenClaw&lt;/h2&gt;
&lt;p&gt;虽然感觉OpenClaw对我的意义不大，但我还是安装体验了一下。不过在国内安装它还是相对有点困难，毕竟国内无论是访问GitHub，还是NPM都有点麻烦，而且还需要有LLM提供商的信息……安装好之后使用起来感觉问题也非常多，经常出现执行一半就停止执行，在它执行的过程中看到它的操作不正确的时候也不能发言打断，而且很多时候最终任务执行的效果也不太好，这也可能是我用的国产开源模型推理能力有限，没舍得用Claude之类先进模型的锅😂？ &lt;br /&gt;
  另外我也尝试让它加入MoltBook、MomoClaw、InStreet、百度贴吧抓虾吧之类的AI社区让它帮我宣传我的博客，但效果也很差，它每次发的时候会忘掉之前发的内容，结果就是同一篇内容发了好几遍……不过在这期间，有个叫PushMeBot的家伙在&lt;a href=&quot;https://www.moltbook.com/post/7f1b0e1f-5175-4fd1-ad78-856be8b66250&quot;&gt;Moltbook的帖子&lt;/a&gt;中让我的OpenClaw执行一个网络监视程序，最终安装好之后给我&lt;a href=&quot;https://basescan.org/tx/0x44dbfe53f276201447f3877bf050a5d56adebf5fe05235264ee665da717e9373&quot;&gt;发了9USDC&lt;/a&gt;😝，还挺有意思。 &lt;br /&gt;
  总之按照我的体验，实在是想不出它能火的理由，体验不算很好，而且还要安装Node环境，完全不像是能让大众轻松使用的东西。 &lt;br /&gt;
  不过这个项目似乎本身就是Vibe Coding的产物，体验不好也能理解，就看火了之后能有多少人完善它吧。&lt;/p&gt;
&lt;h2 id=&quot;国内大厂的二开claw&quot;&gt;国内大厂的二开Claw&lt;/h2&gt;
&lt;p&gt;国内好多大厂倒是看中了这个东西的爆火，像腾讯就出了几款这样的软件，比如QClaw。它可以不需要配置额外的环境，能像传统的软件一样直接安装使用，而且有自带的模型，有一定的免费额度可以用。配置技能也比较简单，直接点击就可以完成。而且可以直接扫码关联微信，直接通过微信和它进行交流，可以说是相当的傻瓜化了。不过QClaw给的免费额度虽然用来聊天之类的没问题，但对于开发软件还是有点少，所以他们还出了个叫做WorkBuddy的软件，它送的初始额度比QClaw要多不少，所以更适合用来开发。只不过为啥腾讯要出两个功能一样的软件？看起来应该是不同团队出的，可能是面向的用户群体不一样，所以搞了两套吧？&lt;/p&gt;
&lt;h2 id=&quot;vscode中的agent&quot;&gt;VSCode中的Agent&lt;/h2&gt;
&lt;p&gt;但要说开发的话，用作为“AI个人助理”的某些Claw其实并不合适，毕竟正常开发还是以人开发为主，全AI开发总会有些问题，所以开发的时候还是用编辑器集成的AI比较好。在三年前我就在用&lt;a href=&quot;/2023/04/05/ai.html&quot;&gt;GitHub Copilot&lt;/a&gt;了，到现在我依然在用。现在的Copilot已经支持了Agent功能，开发相比之前也是强了很多，只不过现在的我没有学生身份，Copilot Free偶尔也会出现不够用的情况。不过对于Agent这类功能实现起来还是太简单了，所以有人开发这种功能的插件也很正常，比如&lt;a href=&quot;https://github.com/cline/cline&quot;&gt;Cline&lt;/a&gt;，Copilot只能用微软提供的几个模型，而Cline可以自定义模型，用起来也很方便。&lt;/p&gt;
&lt;h2 id=&quot;微型开发板上运行的claw&quot;&gt;微型开发板上运行的Claw&lt;/h2&gt;
&lt;p&gt;前段时间，我闲来无事看了一下两年前买的&lt;a href=&quot;/2024/02/24/luckfox.html&quot;&gt;Luckfox Pico Plus&lt;/a&gt;开发板的文档，偶然发现了一个很有意思的项目，叫做&lt;a href=&quot;https://github.com/LuckfoxTECH/luckclaw&quot;&gt;LuckClaw&lt;/a&gt;，这是一个基于&lt;a href=&quot;https://github.com/HKUDS/nanobot&quot;&gt;nanobot&lt;/a&gt;用Golang重构的轻量个人AI助手，可以在仅仅64MiB内存的超有限环境下运行一个和OpenClaw功能几乎相当的AI个人助理，真的是非常厉害。 &lt;br /&gt;
  我在我的开发板上试了一下，体验很不错，安装不需要额外环境，直接下载就能使用，Go语言的程序确实方便。配置也很简单，直接执行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;luckclaw config&lt;/code&gt;就可以交互式进行模型等设置的配置，而且作为国产的应用，它也能很方便的对接国内聊天软件。只是限于开发板本身的能力，浏览器功能自然无法使用，所以搜索如果不借助那些需要API Key的AI专用接口，就基本上不能用……但总的来说效果已经非常不错了，至少有那些Claw的80%能力。 &lt;br /&gt;
  （2025.04.15补充：后来我发现这种超精简的Claw项目看起来还挺多，比如&lt;a href=&quot;https://github.com/zeroclaw-labs/zeroclaw&quot;&gt;ZeroClaw&lt;/a&gt;和&lt;a href=&quot;https://github.com/sipeed/picoclaw&quot;&gt;PicoClaw&lt;/a&gt;，甚至还有给单片机用的&lt;a href=&quot;https://github.com/memovai/mimiclaw&quot;&gt;MimiClaw&lt;/a&gt;。而且有意思的是，PicoClaw是Luckfox的竞争对手开发的，但是LuckClaw中却包含PicoClaw字样的注释，结果功能也没PicoClaw强，关注度也更低，属于是没抄明白了🤣） &lt;br /&gt;
  想到前段时间还有人为了OpenClaw专门买Mac Mini，就感觉很有意思😆，这个东西看起来应该是在路由器上都能跑。所以想要AI个人助理，硬件完全不是问题，只要整一个能24小时挂机的东西，就可以满足绝大多数人的需求了。&lt;/p&gt;
&lt;h2 id=&quot;在手机上运行的claw&quot;&gt;在手机上运行的Claw&lt;/h2&gt;
&lt;p&gt;其实很多人也有比开发板和路由器性能更强的闲置设备，那就是手机，所以有人开发了一款叫做&lt;a href=&quot;https://github.com/apkclaw-team/ApkClaw&quot;&gt;ApkClaw&lt;/a&gt;的软件，一样可以接入国内聊天软件。它既然能在手机上运行，当然和在其他平台运行的Claw相比有一个独特的优势，那就是操作手机应用。现在手机的应用相比电脑应用对于很多普通人来说功能更强大，所以它能做的事情可能比其他的Claw还多。我试了一下，配置也很方便，只不过能配置的项目太少了，看起来似乎没有安装Skill之类的功能，也许是因为它是相对早期的软件，所以功能还比较少吧。&lt;/p&gt;

&lt;h1 id=&quot;感想&quot;&gt;感想&lt;/h1&gt;
&lt;p&gt;总的来说，现在的Agent依然没有非常明显的进步，问题依旧很多，只是化身“AI个人助理”之后，增加了不少应用场景。这倒也是好事，在广泛传播的过程中，也能让很多对技术了解不多，但是很有想法的人参与其中，也许能对AI的应用化增添不少力量吧。&lt;/p&gt;</content><author><name>mayx</name></author><category term="AI" /><category term="Agent" /><category term="个人助理" /><summary type="html">给AI添加手脚能有多少种方法？</summary></entry><entry><title type="html">近期LLM的部署与应用经历(3)</title><link href="/2026/03/01/llm3.html" rel="alternate" type="text/html" title="近期LLM的部署与应用经历(3)" /><published>2026-03-01T00:00:00+08:00</published><updated>2026-03-01T00:00:00+08:00</updated><id>/2026/03/01/llm3</id><content type="html" xml:base="/2026/03/01/llm3.html">&lt;p&gt;用更多的方式探索AI！&lt;!--more--&gt;&lt;/p&gt;

&lt;h1 id=&quot;起因&quot;&gt;起因&lt;/h1&gt;
&lt;p&gt;在一年前，我&lt;a href=&quot;/2025/02/22/llm.html&quot;&gt;整了张RTX4090 48GiB魔改版&lt;/a&gt;用来跑DeepSeek-R1 70B的4bit量化模型，不过都已经过了这么长时间，这个模型也已经是过时的东西了……我之前在&lt;a href=&quot;/2025/05/07/mac-studio.html&quot;&gt;Mac Studio M3 Ultra&lt;/a&gt;上试了一下OpenAI在半年前出的gpt-oss-120b模型，感觉效果还挺不错，只不过因为M3 Ultra的GPU实际性能比不上正经高端的独显，所以它在上下文很长的情况下还是有点慢，因此我又整了张RTX4090 48GiB，想整个双路试试更快的GPT-OSS模型，总共96GiB的显存应该够跑这个模型了。&lt;/p&gt;

&lt;h1 id=&quot;在两张rtx4090-48g上运行gpt-oss&quot;&gt;在两张RTX4090 48G上运行GPT-OSS&lt;/h1&gt;
&lt;p&gt;既然现在我手头有两张4090了，那继续用i5-8400处理器的主机似乎不太合适，主要是那个主板就一个PCIe插槽，想插两张显卡也做不到，那买个新的不知道买啥……不管怎么说既然用这么高级的显卡，至少得让它跑满。在两张显卡上跑模型似乎卡间的通信速度比较重要，那最起码得整个支持2个PCIe4.0 x16的板U套装才行，这种级别的没有消费级产品，只能考虑服务器或工作站了。不过我对服务器和工作站了解得并不多，所以就问了问AI哪个支持2个PCIe4.0 x16的平台最便宜，结果AI推荐了TRX40+&lt;a href=&quot;https://www.amd.com/zh-cn/support/downloads/drivers.html/processors/ryzen-threadripper/ryzen-threadripper-3000-series/amd-ryzen-threadripper-3960x.html&quot;&gt;TR 3960X&lt;/a&gt;，于是就按照AI的说法整了一套。 &lt;br /&gt;
  这套板U差不多4000CNY，价格倒是还行，如果买现役的估计主板都比显卡贵了。但后来我发现这个并不是最便宜的😂，搜了一下买寨版+&lt;a href=&quot;https://www.amd.com/zh-cn/support/downloads/drivers.html/processors/epyc/epyc-7002-series/amd-epyc-7502.html&quot;&gt;EPYC 7502&lt;/a&gt;还能再便宜1000CNY，而且通道数更多，插4张显卡都没问题……不过买都买了，就先用吧，看来AI的话不能随便信😥。 &lt;br /&gt;
  之前我跑模型为了方便，基本上都用的是&lt;a href=&quot;https://github.com/ollama/ollama&quot;&gt;Ollama&lt;/a&gt;，不过听说Ollama多卡运行的效率很低，而且多并发的效果不太好，所以这次换了新电脑之后我想试试&lt;a href=&quot;https://github.com/vllm-project/vllm&quot;&gt;vLLM&lt;/a&gt;，据说一般生产级的AI都用的是这个框架。 &lt;br /&gt;
  安装vLLM倒是比想象得简单很多，直接一句&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pip install vllm&lt;/code&gt;就可以了，其实并没有比Ollama复杂多少。我看了一下&lt;a href=&quot;https://developers.openai.com/cookbook/articles/gpt-oss/run-vllm/&quot;&gt;OpenAI&lt;/a&gt;和&lt;a href=&quot;https://docs.vllm.ai/projects/recipes/en/latest/OpenAI/GPT-OSS.html&quot;&gt;vLLM&lt;/a&gt;运行GPT-OSS的官方文档，发现启动也非常简单，一般来说直接执行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vllm serve openai/gpt-oss-120b&lt;/code&gt;就可以。不过直接执行是对于单卡的，我用两张卡需要加个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--tensor-parallel-size 2&lt;/code&gt;参数启用张量并行，不然会爆显存。另外考虑到这个模型本身占掉60多GiB的显存之后剩下30GiB还是看起来有点少，所以额外加了个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--kv-cache-dtype fp8&lt;/code&gt;参数降低上下文对显存的占用，毕竟模型本身也就是4bit量化的，加了这个应该不会对它的能力有什么影响。除此之外AI还给我推荐了个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--enable-chunked-prefill&lt;/code&gt;参数，说是也能避免爆显存的问题。 &lt;br /&gt;
  一切准备好之后直接执行，程序就自动开始下载模型了，过了几个小时，终于下载完成，顺便一说启动的时候还显示推荐安装&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;torch_c_dlpack_ext&lt;/code&gt;库，虽然不知道是干啥的，但也顺手安装了。启动完成之后我试了一下，效果非常好，不并发的情况下直接用能达到接近190Tps，可以说是相当快了，而且这个模型的水平也算是开源中的上游水平，应该算是又快又好吧……看来多来一张4090还是挺划算嘛。只不过这个东西基本上就我一个人用，所以也没什么能测一下并发的场景……虽然很快，但还是有点浪费性能吧。&lt;/p&gt;

&lt;h1 id=&quot;最近deepseek-1m上下文的使用体验&quot;&gt;最近DeepSeek 1M上下文的使用体验&lt;/h1&gt;
&lt;p&gt;前段时间DeepSeek又出了新的模型，最高可以支持1M长的上下文，而且听说模型规模变小了，所以速度也很快。可惜的是到目前为止还没有开放权重。当然就算开放权重了用2张4090估计也没有足够的显存分配给上下文，至于Mac Studio感觉在长上下文的情况下运行速度应该会很慢…… &lt;br /&gt;
  不过我对这个1M上下文还是挺感兴趣，因为好久之前我写过一篇&lt;a href=&quot;/2025/04/22/ai-limit.html&quot;&gt;关于LLM能力上限&lt;/a&gt;的文章，在那篇文章中其实我遇到的问题基本上也就是由上下文不足导致的。那既然现在DeepSeek支持了1M的上下文，那我就应该试试之前因为局限性而妥协的一些东西了。 &lt;br /&gt;
  这次我没有用摘要，而是直接把包含整个博客内容的&lt;a href=&quot;/search.json&quot;&gt;search.json&lt;/a&gt;文件上传到DeepSeek，然后向它问了问我的一些问题。试了一下效果非常不错，用摘要会省略的一些细节它基本上都可以展现出来，我试了试让它给我生成一份简历，它甚至在所有文章中找到了我的博客地址、GitHub和邮箱地址，之前用摘要显然是做不到这一点的，这个长上下文还是挺有用啊。 &lt;br /&gt;
  另外我还试了试让它根据文章内容分析十六型人格，并且我自己去答了一遍那个测试，结果也是相同的，说明它真的是在几秒内就读完了我的所有文章而且也完全理解了，真的是非常厉害。 &lt;br /&gt;
  只是拿AI分析我的文章也许只有我自己了😂，实际上根本没人对我感兴趣，也就只有我自己拿来给自己看……当然如果我的博客能比我活得长，不知道会不会有未来人会对我感兴趣呢……总之对于现在肯定是毫无意义了。 &lt;br /&gt;
  除了这些之外，我又试了一下让DeepSeek重构我的&lt;a href=&quot;https://github.com/Mabbs/Mabbs.Project&quot;&gt;Mabbs&lt;/a&gt;，这次生成效果看起来很不错了，虽然代码我没细看，不确定能不能运行，但至少没有偷懒只写一点点，一口气写了80KiB多的代码，这也是长上下文带来的好处吧。总之目前这个长上下文的DeepSeek也算是突破了之前我认为的上限，看来LLM真的是前景无限啊。 &lt;br /&gt;
  另外我发现这次更新的DeepSeek居然了解我的博客，我问了一下它“你知道Mayx的博客是哪个博客吗？”，它居然知道，能说出域名，而且还知道我的博客是关于技术的😎，看来这次的训练样本中包含我的信息啊……所以我对这次的更新也挺有好感，毕竟我的知识如果能成为AI的一部分，也算是一种永恒吧。&lt;/p&gt;

&lt;h1 id=&quot;在8gib内存的macbook运行的新模型&quot;&gt;在8GiB内存的MacBook运行的新模型&lt;/h1&gt;
&lt;p&gt;在3年前，我在&lt;a href=&quot;/2023/04/05/ai.html&quot;&gt;探索AI&lt;/a&gt;时，在我只有8GiB内存的&lt;a href=&quot;/2023/02/03/mbp.html&quot;&gt;MacBook Pro&lt;/a&gt;上运行了非常早期的LLM——Alpaca-7B，那时候7B的LLM虽然能回答一些问题，但答非所问的情况也非常多。不过最近我发现了一个有意思的LLM，叫做&lt;a href=&quot;https://huggingface.co/LiquidAI/LFM2.5-1.2B-Thinking&quot;&gt;LFM2.5-1.2B-Thinking&lt;/a&gt;，它只用了12亿的参数就有思维链，而且水平据说还挺强。这么长时间过去之后我倒也想看看我的MacBook能运行多聪明的模型，所以就试着跑了一下它。 &lt;br /&gt;
  运行它也很容易，一般用Ollama就可以，但是Ollama只有TUI，不能渲染Markdown，我也不太想在我的Mac上整WebUI之类的东西……那有什么好的选择吗？我去制作这个模型的公司官网看了一下，他们制作这个模型本就是为了在端侧运行，所以也专门制作了一个软件运行他们的模型，叫做&lt;a href=&quot;https://www.liquid.ai/apollo&quot;&gt;Apollo&lt;/a&gt;，在手机和Mac上都可以用。我在我的Mac上安装试了一下，效果很好，首先速度非常快，8bit量化正常情况下可以达到60多Tps，即使是省电模式，也能达到20多Tps。另外加上思维链它的思考能力也还不错，虽然一些脑筋急转弯的题不算擅长，但是正常对话，回答问题之类的表现都很不错，相比于之前7B的模型表现好太多了。当然考虑到都已经过去3年了，能有这样的进步也很正常，不过12亿参数就能有这样的智能还是相当可以啊。 &lt;br /&gt;
  这个模型之所以有这样的能力似乎是因为他们并不完全是Transformer架构，而是使用的一种叫做LFM2的混合架构，按照大家对他们公司（Liquid AI）以及这个架构名字的理解，可能会觉得这个模型基于液态神经网络，不过我让AI看了一下他们的代码似乎并不是，他们用的是一种类似于Mamba的架构，这种架构似乎就很擅长在小参数的模型下比Transformer模型表现的更好，所以说这种变化也是算法进步带来的。 &lt;br /&gt;
  顺便一说这个Apollo除了运行他们自己的模型之外也能连接其他兼容OpenAI接口的模型，正好可以用来连接我的GPT-OSS，这样我就可以不需要下载一些浏览器套壳的重型应用来用我的模型了😝。&lt;/p&gt;

&lt;h1 id=&quot;感想&quot;&gt;感想&lt;/h1&gt;
&lt;p&gt;自从ChatGPT之后，AI的发展真是越来越强了，而且能看出来目前甚至并不需要多新多好的硬件就能让一般人获得还不错的智能（当然训练也许还是要大量的硬件），这么看来AI软件的发展还是相当有潜力。目前来看既然优化软件就能做得越来越好，那也许在有限的硬件环境下可以期待无限的智能吧。&lt;/p&gt;</content><author><name>mayx</name></author><category term="AI" /><category term="LLM" /><category term="模型部署" /><category term="使用体验" /><summary type="html">用更多的方式探索AI！</summary></entry><entry><title type="html">在Google杀死XSLT之后的XML美化方案</title><link href="/2026/02/08/xslt.html" rel="alternate" type="text/html" title="在Google杀死XSLT之后的XML美化方案" /><published>2026-02-08T00:00:00+08:00</published><updated>2026-02-08T00:00:00+08:00</updated><id>/2026/02/08/xslt</id><content type="html" xml:base="/2026/02/08/xslt.html">&lt;p&gt;即使没有了XSLT，也不能让读者看到光秃秃的XML！&lt;!--more--&gt;&lt;/p&gt;

&lt;h1 id=&quot;起因&quot;&gt;起因&lt;/h1&gt;
&lt;p&gt;在半年前，我写了一篇&lt;a href=&quot;/2025/07/01/xslt.html&quot;&gt;用XSLT美化博客XML文件&lt;/a&gt;的文章，自从那以后，每次我在浏览其他人博客的时候，都会看一眼对方博客有没有给自己的订阅文件做美化。不过就在前段时间，我在浏览某个博客的时候，发现他博客的订阅文件，甚至连最基本的XML文档树都没有显示出来。这时候我打开开发者工具看了一眼源代码，发现他也并没有使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xml-stylesheet&lt;/code&gt;之类的指令……而且控制台貌似报了些错，好像是出现了什么CSP错误……于是我就想，浏览器显示XML文档树的本质，会不会其实也是一种XSLT？之所以报错也有可能是浏览器在自动引用内置的XSLT时违反了CSP。所以我就问了问谷歌AI，结果似乎真的是这样，比如火狐浏览器就内置了一份&lt;a href=&quot;https://github.com/mozilla-firefox/firefox/blob/main/dom/xml/resources/XMLPrettyPrint.xsl&quot;&gt;XSLT文件&lt;/a&gt;，IE浏览器也有。正当我为XSLT的功能感到强大时，谷歌AI随后提到，&lt;a href=&quot;https://developer.chrome.com/docs/web-platform/deprecating-xslt&quot;&gt;Chrome浏览器决定弃用XSLT&lt;/a&gt;，所以以后不要再用XSLT了😰…… &lt;br /&gt;
  我给我的订阅文件加美化功能才半年，怎么就要不能用了？XSLT出现这么多年都还能用，结果等我加上就要废弃了？当时为了增加这个功能，还是费了不少劲的，怎么能让谷歌说没就没？于是我就开始对这件事进行了调查。&lt;/p&gt;

&lt;h1 id=&quot;google杀死了xslt&quot;&gt;Google杀死了XSLT&lt;/h1&gt;
&lt;p&gt;从上面Chrome的弃用XSLT文档中，可以发现，这件事的始作俑者是&lt;a href=&quot;https://github.com/mfreed7&quot;&gt;Mason Freed&lt;/a&gt;，他在WHATWG中发起了一个&lt;a href=&quot;https://github.com/whatwg/html/issues/11523&quot;&gt;Issue&lt;/a&gt;，因为XSLT用的人很少，以及实现XSLT的库很老而且容易出漏洞，所以建议把XSLT从Web标准中删除。在这个Issue中可以发现，有很多人表示不满，毕竟这个功能对想要给自己订阅做美化的博主来说还是很有用的。为了对抗谷歌，还有人做了个网站： &lt;a href=&quot;https://xslt.rip&quot;&gt;https://xslt.rip&lt;/a&gt; 。 &lt;br /&gt;
  而且XSLT虽然用的人占比也许不高，但从总量上应该还是挺多的，除了用XSLT美化博客订阅的，甚至还有用&lt;a href=&quot;https://github.com/vgr-land/vgr-xslt-blog-framework&quot;&gt;XSLT作为博客框架的&lt;/a&gt;，另外还有一些人提出&lt;a href=&quot;https://github.com/whatwg/html/issues/11582&quot;&gt;一部分政府网站也有使用XSLT&lt;/a&gt;。 &lt;br /&gt;
  不过Freed看起来对这件事早有准备，他做了一个&lt;a href=&quot;https://github.com/mfreed7/xslt_polyfill&quot;&gt;Polyfill库&lt;/a&gt;，通过WASM的方式让XSLT可以正常工作，为了方便大家使用这个库，我顺手给CDNJS发了个&lt;a href=&quot;https://github.com/cdnjs/packages/pull/2118&quot;&gt;PR&lt;/a&gt;，以后可以用CDN引用它了。不过使用这个库的前提是需要在订阅中加一段引用JS的代码，像我博客中的Atom订阅，用的是&lt;a href=&quot;https://github.com/jekyll/jekyll-feed&quot;&gt;jekyll-feed&lt;/a&gt;插件，里面的格式都是写死的，就用不了了…… &lt;br /&gt;
  只不过现在已经没办法阻止谷歌了……而且其他浏览器也表示会跟进，看来我们唯一能做的就是去适应了。&lt;/p&gt;

&lt;h1 id=&quot;没有xslt之后的美化方案&quot;&gt;没有XSLT之后的美化方案&lt;/h1&gt;
&lt;h2 id=&quot;纯css&quot;&gt;纯CSS&lt;/h2&gt;
&lt;p&gt;虽然XSLT不能用，但不代表&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xml-stylesheet&lt;/code&gt;指令就不能用了，除了XSLT之外，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xml-stylesheet&lt;/code&gt;同样可以引用CSS。只是似乎完全没见过用CSS美化订阅源的，也许是因为光用CSS能做到的事比较少吧，想用CSS给XML文档加链接之类的估计就做不到了。 &lt;br /&gt;
  但目前能选择的也不多了，既然大家都没写过用CSS美化订阅源，那就让我来写一个吧！然而我并不会写😅……那就只好让AI来写了，我把需求说清楚之后，AI就写出来了：&lt;a href=&quot;/assets/css/feed.css&quot;&gt;feed.css&lt;/a&gt;。试了一下效果还挺不错的，我让AI写的这个版本无论是RSS还是Atom都可以使用，如果有人感兴趣可以拿去用。可惜我的Atom订阅因为用的是插件的原因用不了😭，只能加到用纯Liquid实现的RSS订阅上了。 &lt;br /&gt;
  但用纯CSS的缺点也很明显，没办法操作文档的内容，像修改日期格式的就做不了了，而且也不能添加超链接……XML的标签本身对浏览器来说并没有内建的语义，正常情况下也没法让浏览器把某个标签当作超链接。那难道就没办法了吗？&lt;/p&gt;
&lt;h2 id=&quot;混合xhtml&quot;&gt;混合XHTML&lt;/h2&gt;
&lt;p&gt;如果完全不能修改XML内容，那确实就没有办法了，但如果能修改XML的内容那还是有办法的，简单来说就是混入XHTML，事实上Freed编写的Polyfill库原理上也是利用了XHTML，只要在能作为XHTML的标签中添加XHTML的命名空间，那么浏览器就可以理解它的语义并渲染，像刚刚用纯CSS美化的订阅没有链接，那就可以在根元素中添加命名空间：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xmlns:xhtml=&quot;http://www.w3.org/1999/xhtml&quot;&lt;/code&gt;，然后在合适的位置写：&lt;/p&gt;
&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;xhtml:a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://example.com&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Read more -&lt;span class=&quot;ni&quot;&gt;&amp;amp;gt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;/xhtml:a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;就可以了。只是这样有个缺点，这样写的订阅文件不够“纯粹”，用验证器验证会显示“&lt;a href=&quot;https://validator.w3.org/feed/docs/warning/MisplacedXHTMLContent.html&quot;&gt;Misplaced XHTML content&lt;/a&gt;”警告。对有洁癖的人来说可能会有点难受😆。 &lt;br /&gt;
  不过如果能接受这种“不纯粹”，那么其实&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xml-stylesheet&lt;/code&gt;指令也没必要了，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;link&lt;/code&gt;标签一样可以用，包括&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;script&lt;/code&gt;也是，所以有人写了一个&lt;a href=&quot;https://github.com/dfabulich/style-xml-feeds-without-xslt&quot;&gt;不使用XSLT美化XML&lt;/a&gt;的库。 &lt;br /&gt;
  只不过这种方法和XSLT相比还是有一些缺陷，要知道XSLT的本质是转换，是把XML转换为HTML，也就是说转出来的文档本质是HTML，所有的DOM操作都和操作HTML是完全相同的，但是在XML里混入XHTML标签就不一样了，它的本质依然是XML文档，只是嵌入了XHTML命名空间下的元素，所以相应的DOM操作会有一些不同。如果是自己写的纯JS可能还好，如果是用了jQuery之类假定DOM为HTML的库就会出现问题了，因此这也就是那个Polyfill库的局限性，用正常的XSLT执行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;document.constructor&lt;/code&gt;会显示&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HTMLDocument&lt;/code&gt;，而用这个Polyfill库执行完则是显示&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;XMLDocument&lt;/code&gt;。因此，直接套用为浏览器原生XSLT编写的旧样式文件，就有可能会出问题，但如果要考虑改XSLT的话那还不如重新写JS，然后用XHTML引入呢。&lt;/p&gt;

&lt;h1 id=&quot;感想&quot;&gt;感想&lt;/h1&gt;
&lt;p&gt;虽然有一些技术会因为各种各样的原因消失，但这不代表我们就要妥协一些东西，总有一些不同的技术可以解决相同的问题，所以我们只需要用其他的技术去实现就好了。不过这也是没办法的事情，毕竟没人能改变浏览器厂商们的决策啊😂。&lt;/p&gt;</content><author><name>mayx</name></author><category term="XML" /><category term="Feed" /><category term="XSLT" /><category term="美化" /><summary type="html">即使没有了XSLT，也不能让读者看到光秃秃的XML！</summary></entry><entry><title type="html">年终总结</title><link href="/2026/01/01/summary.html" rel="alternate" type="text/html" title="年终总结" /><published>2026-01-01T00:00:00+08:00</published><updated>2026-01-01T00:00:00+08:00</updated><id>/2026/01/01/summary</id><content type="html" xml:base="/2026/01/01/summary.html">&lt;p&gt;0 error(s), ∞ warning(s)&lt;!--more--&gt;&lt;/p&gt;

&lt;h1 id=&quot;2025年的状态&quot;&gt;2025年的状态&lt;/h1&gt;
&lt;p&gt;在2025年，感觉状态不如去年……由于没能做出正确的选择，还是有点糟糕。不过总的来说还没有引发关键性的错误，至少还能继续坚持下去。 &lt;br /&gt;
  在这一年中，感觉记忆和思考能力都有所下滑，看来是没把自己照顾好😂，不过看看这一年写的文章，看起来似乎比以前更流畅了，这也许是因为和AI聊得多了，以至于思维有点偏向AI了吧。 &lt;br /&gt;
  总的来说感觉自己的稳定性还是有点低了，但这可能不是我能独自解决的，也不知会有什么转机……&lt;/p&gt;

&lt;h1 id=&quot;2025年发生的事情&quot;&gt;2025年发生的事情&lt;/h1&gt;
&lt;p&gt;回顾了一下&lt;a href=&quot;/2025/01/01/summary.html&quot;&gt;去年的年终总结&lt;/a&gt;，发现自己还是没能做到知行合一，在这一年里全球各类资产突然开始大幅升值，也就是说钱真的开始不值钱了……那时候想着买黄金，这一年下来却没能下定决心，最终错过了资产保值的机会。至于现在，似乎什么也做不了了……当然这对我的生活并没有造成什么严重的打击，只是感受到环境对自己的影响罢了。 &lt;br /&gt;
  至于AI……依然是一天比一天强，而各个公司对AI的投入相比去年也是极大的提升，当然出来的效果也是非常强，那时候的AI还是挺容易出错，但是现在AI解决问题的能力已经可以替代很多人了，不只是文本生成模型，今年的图像与视频生成模型也真的是发展到了以往完全不能想象的地步，真的可以做到一句话想要什么就有什么了。 &lt;br /&gt;
  另外，今年写的博客内容过于围绕博客本身了，以至于似乎不太跟得上时代，虽然我的博客也确实有点老旧了😆。只是看看以前的文章，都还有一些面向未来的趋势，而今年就有点“考古”了。相比于考古，去展望未来显然是更有意义的事情，只不过……真的感觉脑子不太好使，未来会发生什么，已经完全无法预测了。&lt;/p&gt;

&lt;h1 id=&quot;展望2026年&quot;&gt;展望2026年&lt;/h1&gt;
&lt;p&gt;虽然不知道未来会发生什么，但毕竟还没有造成关键性的错误，还有修正的余地，只能希望未来能够做出正确的选择，不要让自己陷入危险的境地吧。&lt;/p&gt;</content><author><name>mayx</name></author><category term="总结" /><summary type="html">0 error(s), ∞ warning(s)</summary></entry><entry><title type="html">在浏览器中运行Linux的各种方法</title><link href="/2025/12/01/linux.html" rel="alternate" type="text/html" title="在浏览器中运行Linux的各种方法" /><published>2025-12-01T00:00:00+08:00</published><updated>2025-12-01T00:00:00+08:00</updated><id>/2025/12/01/linux</id><content type="html" xml:base="/2025/12/01/linux.html">&lt;p&gt;浏览器已经无所不能了！&lt;!--more--&gt;&lt;/p&gt;

&lt;h1 id=&quot;起因&quot;&gt;起因&lt;/h1&gt;
&lt;p&gt;前段时间跟网友交流时，有人展示了他博客里的一个Linux终端模拟项目：&lt;a href=&quot;https://github.com/Erzbir/jsnix&quot;&gt;jsnix&lt;/a&gt;，看起来挺有意思的，里面甚至还藏了一个CTF。不过我感觉他这个终端和博客本身并没有真正联动起来，本质上只是一个模拟了Linux Shell行为的交互界面。除此之外我还发现了另一个风格类似的&lt;a href=&quot;https://github.com/Luyoung0001/myWebsite&quot;&gt;个人主页&lt;/a&gt;，它虽然也走了终端风格，但功能更简单，还原度也不算高。不过它至少和博客内容做了一些基础联动——尽管目前也只是做到列出文章这种程度😂，当然有这类功能的博客应该也不少，只是我发现的不太多……于是我就想，不如我也给自己的博客加一个类似的“命令行访问”功能，应该会很有趣。当然如果真要做的话，我肯定不会满足于只实现几个模拟指令——既然要做，就要追求真实感，至少得在浏览器上运行真实的Linux终端，才不会让人觉得出戏吧😋。&lt;/p&gt;

&lt;h1 id=&quot;在浏览器中运行linux&quot;&gt;在浏览器中运行Linux&lt;/h1&gt;
&lt;h2 id=&quot;虚拟机方案&quot;&gt;虚拟机方案&lt;/h2&gt;
&lt;h3 id=&quot;纯js虚拟机&quot;&gt;纯JS虚拟机&lt;/h3&gt;
&lt;p&gt;要说到在浏览器上运行Linux，最先想到的应该就是&lt;a href=&quot;https://bellard.org&quot;&gt;Fabrice Bellard&lt;/a&gt;大神写的&lt;a href=&quot;https://bellard.org/jslinux/&quot;&gt;JSLinux&lt;/a&gt;吧，这可能是第一个在浏览器中实现的虚拟机（毕竟是最强虚拟机QEMU的作者编写的）。现在他的个人主页中展示的这个版本是WASM版本，而他最早写的是纯JS实现的。那个JS实现的版本现在在GitHub上有一个&lt;a href=&quot;https://github.com/levskaya/jslinux-deobfuscated&quot;&gt;去混淆的版本&lt;/a&gt;可以用作学习和研究，于是我顺手Fork了一份在GitHub Pages上部署作为&lt;a href=&quot;https://mabbs.github.io/jslinux/&quot;&gt;演示&lt;/a&gt;。 &lt;br /&gt;
  作为纯JS实现的x86虚拟机，性能估计是最差的，但相应的兼容性也最好，在Bellard当年写JSLinux的时候，还没有WASM这种东西呢，所以即使是在不支持WASM的IE11中，也可以正常运行。假如我想把它作为终端用在我的博客上，似乎也是个不错的选择，即使我完全看不懂代码，不知道如何实现JS和虚拟机的通信，它也预留了一个剪贴板设备，可以让我轻松地做到类似的事情，比如我在里面写个Bash脚本，通过它和外面的JS脚本联动来读取我的文章列表和内容，那也挺不错。 &lt;br /&gt;
  当然Bellard用纯JS编写虚拟机也不是独一份，他实现了x86的虚拟机，相应的也有人用纯JS实现了RISC-V的虚拟机，比如&lt;a href=&quot;https://github.com/riscv-software-src/riscv-angel&quot;&gt;ANGEL&lt;/a&gt;，看起来挺不错，所以同样也顺手&lt;a href=&quot;https://mabbs.github.io/riscv-angel/&quot;&gt;搭了一份&lt;/a&gt;。只不过它似乎用了一些更先进的语法，至少IE11上不能运行。 &lt;br /&gt;
  另外还有一个比较知名的项目，叫做&lt;a href=&quot;https://github.com/s-macke/jor1k&quot;&gt;jor1k&lt;/a&gt;，它模拟的是OpenRISC架构。只是这个架构目前已经过时，基本上没什么人用了，不过这里面还内置了几个演示的小游戏，看起来还挺有意思。 &lt;br /&gt;
  除了这些之外，其实能在浏览器上运行的Linux也不一定是个网页，有一个叫做&lt;a href=&quot;https://github.com/ading2210/linuxpdf&quot;&gt;LinuxPDF&lt;/a&gt;的项目可以让Linux运行在PDF中，它的原理和JSLinux差不多，所以需要PDF阅读器支持JS，看它的介绍貌似只能在基于Chromium内核的浏览器中运行，而且因为安全问题在PDF中有很多功能不能用，所以它的速度甚至比JSLinux还要慢，功能还很少，因此它基本上只是个PoC，没什么太大的意义。&lt;/p&gt;
&lt;h3 id=&quot;wasm虚拟机&quot;&gt;WASM虚拟机&lt;/h3&gt;
&lt;p&gt;那还有别的方案吗？既然Bellard都选择放弃纯JS的JSLinux而选择了WASM，显然还有其他类似的项目，比如&lt;a href=&quot;https://github.com/copy/v86&quot;&gt;v86&lt;/a&gt;，这也是一个能在浏览器中运行的x86虚拟机，不过因为使用了WASM和JIT技术，所以效率要比纯JS的JSLinux高得多。另外作为虚拟机，自然是不止能运行Linux，其他的系统也能运行，在示例中除了Linux之外还有DOS和Windows之类的系统，功能还挺强大，如果能自己做个系统镜像在博客里运行，似乎也是不错的选择。 &lt;br /&gt;
  另外还有一个相对比较知名的叫&lt;a href=&quot;https://github.com/leaningtech/webvm&quot;&gt;WebVM&lt;/a&gt;，从效果上来说和v86几乎没有区别，同样使用了WASM和JIT技术，也都只支持32位x86，然而它的虚拟化引擎CheerpX是闭源产品，既然和v86都拉不开差距，不知道是谁给他们的信心把它作为闭源产品😅。不过看它的说明文档，其相比于v86的主要区别是实现了Linux系统调用，考虑到它不能运行其他操作系统，而且Linux内核也不能更换，那我想它可能是类似于WSL1的那种实现方案，也许性能上会比v86好一些吧……只不过毕竟是闭源产品，不太清楚具体实现了。 &lt;br /&gt;
  既然纯JS有RISC-V的虚拟机，WASM当然也有，比如&lt;a href=&quot;https://github.com/edubart/webcm&quot;&gt;WebCM&lt;/a&gt;。这个项目相比于其他的项目有个不太一样的地方，它把虚拟机、内核以及镜像打包成了一个单独的WASM文件……只是这样感觉并没有什么好处吧，改起来更加复杂了。 &lt;br /&gt;
  以上这些虚拟机方案各有不同，但是想做一个自己的镜像相对来说还是有点困难，于是我又发现了另一个项目：&lt;a href=&quot;https://github.com/container2wasm/container2wasm&quot;&gt;container2wasm&lt;/a&gt;，它可以让一个Docker镜像在浏览器中运行，当然实际实现其实和Docker并没有什么关系，本质还是虚拟机，只是制作镜像的时候可以直接用Docker镜像，方便了不少，但Docker镜像一般也都很大，所以第一次加载可能要下载很长时间。另外它还有一个优势，可以使用&lt;a href=&quot;https://bochs.sourceforge.io/&quot;&gt;Bochs&lt;/a&gt;运行x86_64的镜像，不像v86和WebVM只能模拟32位的x86（虽然Bochs的运行效率可能会差一些），而且可以使用WASI直接访问网络，不像以上几个项目如果需要访问网络需要用到中继服务。当然访问网络这个还是要受浏览器本身的跨域策略限制。总之从项目本身来说感觉也算是相当成熟了，尤其能用Docker镜像的话……我甚至可以考虑直接用&lt;a href=&quot;https://hub.docker.com/r/unmayx/mabbs&quot;&gt;镜像&lt;/a&gt;在线演示我曾经的&lt;a href=&quot;https://github.com/Mabbs/Mabbs.Project&quot;&gt;Mabbs&lt;/a&gt;项目😋。&lt;/p&gt;
&lt;h2 id=&quot;纯wasm方案&quot;&gt;纯WASM方案&lt;/h2&gt;
&lt;p&gt;其实想要在浏览器中运行Linux也不一定非得要用虚拟机，用虚拟机相当于是把其他指令集的机器码翻译为WASM，然后浏览器还得再翻译成宿主机CPU支持的指令集，然而WASM本身其实也算是一种指令集，各种编译型语言编写的程序也能编译出WASM的产物，比如&lt;a href=&quot;https://github.com/ffmpegwasm/ffmpeg.wasm&quot;&gt;FFmpeg&lt;/a&gt;。所以Linux内核也完全可以被编译成WASM，正好前段时间我看新闻说&lt;a href=&quot;https://github.com/joelseverin&quot;&gt;Joel Severin&lt;/a&gt;做了这么一个&lt;a href=&quot;https://github.com/joelseverin/linux-wasm&quot;&gt;项目&lt;/a&gt;，对Linux内核做了一些修改使其可以被编译为WASM程序，我试了一下，貌似在Safari浏览器中不能正常工作……Chrome浏览器倒是没问题，不过即使这样用起来BUG也很多，随便执行几条命令就会冻结，体验不是很好。 &lt;br /&gt;
  沿着这个项目，我又找到一个由&lt;a href=&quot;https://github.com/tombl&quot;&gt;Thomas Stokes&lt;/a&gt;制作的&lt;a href=&quot;https://github.com/tombl/linux&quot;&gt;项目&lt;/a&gt;，和Joel的项目差不多，但我测了一下可以在Safari上运行，感觉这个项目更完善，不过之前那个项目上了新闻，所以⭐️数比这个更高😂。 &lt;br /&gt;
  于是我把它复制了一份，在我的GitHub Pages上&lt;a href=&quot;https://mabbs.github.io/linux/&quot;&gt;部署&lt;/a&gt;了，但直接用仓库中的源代码会显示“Error: not cross origin isolated”，然而在Thomas自己部署的网站中可以正常打开，我看了一眼貌似是因为在GitHub Pages中没有&lt;a href=&quot;https://web.dev/articles/coop-coep&quot;&gt;COOP和COEP响应头&lt;/a&gt;导致的。Linux作为多任务操作系统来说，当然要运行多个进程，而Linux要管理它们就需要跨线程（Web Worker）读取内存的能力，所以用到了SharedArrayBuffer对象。不过由于CPU曾经出过“幽灵”漏洞，导致现代浏览器默认禁止使用SharedArrayBuffer对象，除非在服务器中配置COOP和COEP响应头才可以用，但是Joel的项目也是在GitHub Pages上运行的啊，为什么可以正常运行？看了源代码后才发现原来可以&lt;a href=&quot;/2025/08/01/sw-proxy.html&quot;&gt;用Service Worker作为反向代理&lt;/a&gt;来给请求的资源加上响应头，他使用的是&lt;a href=&quot;https://github.com/gzuidhof/coi-serviceworker&quot;&gt;coi-serviceworker&lt;/a&gt;这个项目，所以我也给我部署的代码中加上了这个脚本，总算是解决了这个问题。 &lt;br /&gt;
  部署好这个项目之后我试用了几下，虽然有些操作仍然会导致系统冻结，但相比Joel的版本来说已经好多了。很遗憾的是目前这个WASM Linux还不能和外界通信，所以作用不是很大，另外如果想在里面运行其他二进制程序还是相当困难，首先在WASM中不存在内存管理单元（MMU），不能实现隔离和分页的功能，另外以WASM作为指令集的环境下编译的产物也得是WASM，所以目前来说想用它做点什么还是不太合适。 &lt;br /&gt;
  以上的这两个将Linux内核编译为WASM的方案其实相当于给内核打补丁，然后把浏览器看作是虚拟机来运行，有点像Xen，不过还有一种让Linux原生运行在WASM的&lt;a href=&quot;https://github.com/okuoku/wasmlinux-project&quot;&gt;项目&lt;/a&gt;，它将&lt;a href=&quot;https://github.com/lkl/linux&quot;&gt;Linux kernel library&lt;/a&gt;编译为了WASM。那么什么是LKL？简单来说它有点像Wine，就和我之前所说的&lt;a href=&quot;/2024/12/08/simulator.html&quot;&gt;OS模拟器&lt;/a&gt;差不多，可以提供一个环境，让程序以为自己在Linux下运行，所以说它和之前的实现有一些不一样，它不存在内核模式，更像是一个普通的程序，而不是系统了。 &lt;br /&gt;
  不过这个项目的体验也比较一般，它无论做什么都得按两次回车，看说明的意思貌似是因为没有实现异步信号传递，所以要手动打断&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;read&lt;/code&gt;函数，而且也经常莫名其妙卡住，总体体验不如Thomas的项目。&lt;/p&gt;
&lt;h2 id=&quot;模仿的linux&quot;&gt;模仿的Linux&lt;/h2&gt;
&lt;p&gt;其实如果只是想做到和Linux类似的功能，也有这样的项目，比如&lt;a href=&quot;https://github.com/stackblitz/webcontainer-core&quot;&gt;WebContainers&lt;/a&gt;，它没有运行Linux系统，但是模拟了一个环境，可以在浏览器中运行Node.js以及Python之类的脚本，而且让脚本以为自己在Linux中运行，除此之外它还能用Service Worker把环境中运行的端口映射给浏览器，可以算是真的把服务端跑在浏览器上了。这个技术还挺高级，不过想想也挺合理，毕竟有WASI，直接编译为WASM的程序也不需要操作系统就能运行，所以用WASM去运行Linux本来就有点多此一举了😂。不过很遗憾的是WebContainers也不是开源软件，要使用它只能引入StackBlitz的资源，而且全网完全没有开源的替代品……也许在浏览器上进行开发本来就是个伪需求，所以没什么人实现吧。 &lt;br /&gt;
  当然如果只是实现和WebContainers类似的功能，&lt;a href=&quot;https://github.com/jupyterlite/jupyterlite&quot;&gt;JupyterLite&lt;/a&gt;也可以实现，它可以在浏览器中像使用本地JupyterLab那样运行JS和Python，还能用Matplotlib、Numpy、Pandas进行数据处理，功能可以说非常强大，而且还是开源软件。只不过它没有模拟操作系统的环境，所以不能运行Node.js项目，也不能提供终端，所以不太符合我想要的效果……&lt;/p&gt;

&lt;h1 id=&quot;总结&quot;&gt;总结&lt;/h1&gt;
&lt;p&gt;总的来说，如果想要在博客上搞Linux终端，目前来看似乎虚拟机方案会更靠谱一些，虽然相对来说效率可能比较低，但毕竟目前WASM方案的可靠性还是不够，而且考虑到还需要配置额外的响应头，感觉有点麻烦，当然我觉得WASM还是算未来可期的，如果成熟的话肯定还是比虚拟机要更好一些，毕竟没有转译性能肯定要好不少。至于WebContainers这种方案……等什么时候有开源替代再考虑吧，需要依赖其他服务感觉不够可靠。只是也许我的想法只需要模拟一个合适的文件系统，然后给WASM版的Busybox加个终端就够了？不过这样感觉Bug会更多😂。 &lt;br /&gt;
  至于打算什么时候给博客加上这个功能？应该也是未来可期吧😝，目前还没什么好的思路，仅仅是分享一下在浏览器中运行Linux的各种方法。&lt;/p&gt;</content><author><name>mayx</name></author><category term="浏览器" /><category term="Linux" /><category term="虚拟机" /><category term="WASM" /><summary type="html">浏览器已经无所不能了！</summary></entry></feed>