18 Commits
Author SHA1 Message Date
mayx 8cb782d1ff Update 12 files
- /vendor/libarchive/LICENSE
- /vendor/libarchive/worker-bundle.js
- /vendor/libarchive/wasm-gen/libarchive.js
- /vendor/libarchive/wasm-gen/libarchive.wasm
- /sw.js
- /offline.md
- /_layouts/default.html
- /_tools/serv00_post-receive
- /_tools/envs_post-receive
- /_data/proxylist.yml
- /assets/js/offline-page.js
- /assets/js/offline-unpack.js
2026-10-03 07:57:00 +00:00
mayx 7659d3e9d5 Update 7 files
- /images/GEDSC AIRWAY BILL TO CHINA.jpg
- /images/MOS_Demo.png
- /images/MOS_Demo.webp
- /images/GEDSC AIRWAY BILL TO CHINA.webp
- /images/2020.04.06.zip
- /_posts/2018-10-28-mos-reporter.md
- /_posts/2019-06-10-cheat.md
2026-10-02 16:38:55 +00:00
mayx a4cc075205 Update 2 files
- /deploy.sh
- /.readthedocs.yaml
2026-10-01 15:06:06 +00:00
mayx 44d8461ab1 Update 3 files
- /_data/ai-cache.json
- /_data/proxylist.yml
- /deploy.sh
2026-10-01 14:49:18 +00:00
mayx 303f5df01c Update 5 files
- /_data/other_repo_list.csv
- /_data/links.csv
- /_posts/2026-10-01-quine.md
- /_tools/blogquine.py
- /assets/js/pjax.js
2026-09-30 16:01:13 +00:00
mayx 932e310f9d Update 6 files
- /_tools/blogquine.py
- /_tools/serv00_post-receive
- /_tools/envs_post-receive
- /_data/other_repo_list.csv
- /deploy.sh
- /.readthedocs.yaml
2026-09-11 10:30:53 +00:00
mayx 2036ee2c4f Update 5 files
- /_tools/blogquine.py
- /_data/proxylist.yml
- /_layouts/default.html
- /deploy.sh
- /.readthedocs.yaml
2026-09-07 09:23:41 +00:00
mayx a89e95869e Update 2 files
- /_data/ai-cache.json
- /_data/proxylist.yml
2026-09-03 02:35:45 +00:00
mayx c217a52c70 Update 3 files
- /_posts/2026-09-01-vibe-coding2.md
- /_data/links.csv
- /_data/other_repo_list.csv
2026-08-31 16:00:07 +00:00
mayx 4b94d48330 Update 3 files
- /assets/js/blog-console.js
- /_data/proxylist.yml
- /_tools/envs_post-receive
2026-08-06 10:50:42 +00:00
mayx 2ec9345113 Update 3 files
- /assets/js/blog-console.js
- /_data/other_repo_list.csv
- /_data/proxylist.yml
2026-08-06 09:47:54 +00:00
mayx 987c604dea Update 3 files
- /assets/js/blog-console.js
- /BlogCard.svg
- /_layouts/default.html
2026-08-04 13:49:00 +00:00
mayx 8e0b6e11b3 Update 2 files
- /_data/other_repo_list.csv
- /games.md
2026-08-02 08:32:36 +00:00
mayx 317f5bdce7 Update 2 files
- /games.md
- /_data/ai-cache.json
2026-07-31 16:32:56 +00:00
mayx 89a0af5632 Update 5 files
- /_data/other_repo_list.csv
- /_posts/2026-08-01-terminal.md
- /index.html
- /_tools/envs_post-receive
- /games.md
2026-07-31 16:00:40 +00:00
Mayx 798f65eb97 Merge pull request #206 from Jingyuan-Zheng/codex/add-jingyuan-link
Add Jingyuan's blog
2026-07-27 19:50:32 +08:00
Jingyuan Zheng 05998cdc3f Use Chinese friend link details 2026-07-27 13:47:43 +02:00
Jingyuan Zheng 5d87b2c9d4 Add Jingyuan friend link 2026-07-27 13:42:42 +02:00
33 changed files with 4000 additions and 221 deletions

No files matched your search

+5 -2
View File
@@ -12,5 +12,8 @@ build:
- mkdir Mabbs
- curl -L -o Mabbs/README.md https://github.com/Mabbs/Mabbs/raw/main/README.md
- bundle exec jekyll build --destination $READTHEDOCS_OUTPUT/html
- tar czvf MayxBlog.tgz -C $READTHEDOCS_OUTPUT html
- mv MayxBlog.tgz $READTHEDOCS_OUTPUT/html
- python3 _tools/blogquine.py --src-dir blog --quine-dir blog $READTHEDOCS_OUTPUT/html MayxBlog.7z
- curl -L -O https://github.com/ip7z/7zip/releases/download/26.03/7z2603-linux-x64.tar.xz
- tar xvf 7z2603-linux-x64.tar.xz
- ./7zzs a -mx9 -bd -bb0 MayxBlog-new.7z MayxBlog.7z
- mv MayxBlog-new.7z $READTHEDOCS_OUTPUT/html/MayxBlog.7z
+1 -1
View File
@@ -77,7 +77,7 @@
y="0"
class="header"
data-testid="header"
>Mayx's Blog Article</text>
>Mayx's Blog Posts</text>
</g>
</g>
+4 -1
View File
@@ -179,5 +179,8 @@
"/2026/04/14/ai-agent.html": "这篇文章介绍了“AI个人助理”Agent的发展现状和各种尝试。作者体验了OpenClaw、QClaw、WorkBuddy、Cline、LuckClaw和ApkClaw等不同的Agent项目,发现它们在功能、易用性和性能上各有优劣。OpenClaw安装和使用存在困难,国内大厂的QClaw和WorkBuddy则更易于上手,但免费额度有限。作者认为,开发任务更适合在编辑器集成AI中进行,如GitHub Copilot。LuckClaw在微型开发板上运行表现出色,ApkClaw则利用手机的优势操作移动应用。尽管Agent技术仍存在诸多问题,但其应用场景不断拓展,有望吸引更多人参与其中,推动AI应用化进程。",
"/2026/05/01/virtual-net.html": "这篇文章介绍了作者在尝试异地组网搭建虚拟局域网时,对多种组网工具的探索和体验。作者首先尝试了n2n,但由于其项目停止更新且在NAT后的机器间存在掉线问题,最终选择了WireGuard作为主要方案。文章详细描述了WireGuard的配置过程,包括在Linux、OpenWrt和openEuler系统上的安装和配置,并遇到了各种各样的挑战,例如在红米AX3000路由器上找不到内核模块,以及在openEuler上缺少WireGuard相关包。此外,文章还提到了Netmaker和Headscale等WireGuard的控制平面,以及VNT和EasyTier等其他组网工具,并总结了最终选择WireGuard的原因:简单、够用、可靠性不错,且已经投入了大量配置。",
"/2026/06/01/dedupe.html": "这篇文章介绍了如何通过优化游戏资源文件来节约游戏占用的硬盘空间。作者由于Mac只有256GiB的硬盘存储空间,在下载了几十部游戏后空间不足,于是寻找解决方法。文章主要介绍了以下几点:\n\n1. 使用jdupes工具去除重复素材:作者发现许多游戏使用相同的引擎和素材,导致硬盘空间浪费。通过使用jdupes工具进行硬链接,可以有效去除重复文件,节约空间。\n\n2. 不同引擎的处理方式:\n - 对于RPG制作大师MV/MZ,作者使用RPG Maker Decrypter工具解密资源文件,并将图片转换为WebP格式以压缩空间。\n - 对于RPG制作大师XP/VX/VA,作者使用mkxp-z工具跨平台运行游戏,并通过RPG Maker Decrypter解包资源文件,与RTP素材合并后进行去重。\n - 对于Ren'Py游戏,作者使用unrpa工具解包rpa文件,但由于公共资源不多,仅在系列游戏情况下进行解包。\n\n3. 最终效果:通过以上优化,作者成功将游戏文件夹大小从47G降至33G,节约了大量硬盘空间。",
"/2026/07/01/vibe-coding.html": "这篇文章介绍了作者使用AI Agent进行Vibe Coding的体验。作者通过AI Agent生成代码,实现了博客的PJAX功能和一个立直麻将Wordle小游戏。在使用AI Agent的过程中,作者发现它能够理解整个项目的代码,并且能够根据需求进行修改和优化。然而,作者也遇到了一些问题,例如生成的代码质量不高,需要手动优化和调试。\n\n作者首先使用AI Agent为博客添加了PJAX功能,实现了无刷新加载页面。虽然生成的代码质量不高,但作者还是成功地实现了该功能。接着,作者使用AI Agent生成了一个立直麻将Wordle小游戏,并对其进行了优化和修改。作者还遇到了一些技术问题,例如字体兼容性和役种计算问题,但最终都通过AI Agent解决了。\n\n通过这次体验,作者认为Vibe Coding的体验相当不错,但也存在一些问题。完全由AI生成的代码可能会出现问题,需要手动优化和调试。然而,如果能够正确地使用AI Agent,也可以实现高质量的代码。作者还提到,有时候自己调查和研究比问AI更有效,可以避免浪费时间和精力。"
"/2026/07/01/vibe-coding.html": "这篇文章介绍了作者使用AI Agent进行Vibe Coding的体验。作者通过AI Agent生成代码,实现了博客的PJAX功能和一个立直麻将Wordle小游戏。在使用AI Agent的过程中,作者发现它能够理解整个项目的代码,并且能够根据需求进行修改和优化。然而,作者也遇到了一些问题,例如生成的代码质量不高,需要手动优化和调试。\n\n作者首先使用AI Agent为博客添加了PJAX功能,实现了无刷新加载页面。虽然生成的代码质量不高,但作者还是成功地实现了该功能。接着,作者使用AI Agent生成了一个立直麻将Wordle小游戏,并对其进行了优化和修改。作者还遇到了一些技术问题,例如字体兼容性和役种计算问题,但最终都通过AI Agent解决了。\n\n通过这次体验,作者认为Vibe Coding的体验相当不错,但也存在一些问题。完全由AI生成的代码可能会出现问题,需要手动优化和调试。然而,如果能够正确地使用AI Agent,也可以实现高质量的代码。作者还提到,有时候自己调查和研究比问AI更有效,可以避免浪费时间和精力。",
"/2026/08/01/terminal.html": "这篇文章介绍了作者利用AI助手制作终端版博客的经历。起初,作者想在博客上添加一个终端功能,允许用户在浏览器中使用真正的Linux访问博客。作者选择了基于WASM Linux的方案,并让AI助手开发了一个可以在浏览器中执行JS的设备文件,以实现与外界的通信。在此基础上,作者实现了列出文章、查看文章、打开文章和查看评论等功能。然而,该系统存在一些限制,如不能使用管道符和异步操作。作者还尝试了另一种方案,即基于JSNix的版本,虽然不是真正的Linux,但模拟得非常到位,且具有更好的兼容性和加载速度。最终,作者成功制作了两个版本的终端版博客,并表示对AI助手的强大功能感到满意。",
"/2026/09/01/vibe-coding2.html": "这篇文章介绍了作者使用AI进行Vibe Coding的体验。两个月前,作者首次体验了Vibe Coding,但当时对AI的能力持怀疑态度。然而,在使用AI进行终端版博客开发后,作者意识到AI的能力可能超过了自己的想象。作者开始使用国内的免费AI模型腾讯混元Hy3进行各种项目开发,并取得了令人惊讶的成果。\n\n作者使用AI开发了 REPL 终端、WebMCP 项目、博客 API,并将 WebMCP 应用于 AI,让 AI 调用函数。作者还使用 AI 重放了一个即将关服的游戏的离散快照,让游戏在浏览器中运行。AI 分析了游戏的 WebSocket+Protobuf 协议,并用 Python 和 JavaScript 实现了一个服务端,让游戏在浏览器中运行。\n\n此外,作者还使用 AI 制作了一个完整的麻将对局引擎,包括人机对战功能。作者让 AI 录制了几把实际对局的包,并理解了数据包的时序,最终 AI 成功地制作出了完整的对局引擎。\n\n作者还探索了其他功能,如挂载本地文件夹、基于 WebRTC 的局域网内组网和使用 VLESS 协议的网关。",
"/2026/10/01/quine.html": "这篇文章介绍了作者利用AI制作一个“完整”的博客压缩包的过程。起因是作者之前尝试使用ZIP Quine制作包含自身压缩包,但由于技术限制未能成功。作者此次使用AI尝试基于LZMA2算法制作7z格式的博客压缩包,最终成功生成代码并制作完成。文章中还讨论了作者对AI创新能力的看法,认为AI不仅可以重组现有信息,还可以完成人类以前认为具有挑战性的任务。"
}
+3
View File
@@ -24,3 +24,6 @@ RavelloH's Blog,https://ravelloh.com,https://ravelloh.com/feed.xml,Beginning of
初然忆,https://www.imcry.vip,https://www.imcry.vip/index.xml,初心如初见,然忆似流年。
姓王者,https://xingwangzhe.fun/,https://xingwangzhe.fun/rss.xml,探索、学习、进步、创造无限可能!
EXYONE@BLOG:~$,https://exyon.ee,https://exyon.ee/feed.xml,一隅藏天地,流水遇知音。
小景,https://jingyuan-zheng.github.io/zh/,https://jingyuan-zheng.github.io/zh/index.xml,随心分享有趣的技术、开源项目与生活记录。
热汤茶馆,https://blog.hotsouprealm.top/,https://blog.hotsouprealm.top/atom.xml,茶凉了可以续,话断了可以接,这里是hahahotsoup的茶馆。
FGHRSH 日记簿,https://www.fghrsh.net/,https://www.fghrsh.net/feed.php,把喜欢的东西都记下来···
1 title link feed_url description
24 初然忆 https://www.imcry.vip https://www.imcry.vip/index.xml 初心如初见,然忆似流年。
25 姓王者 https://xingwangzhe.fun/ https://xingwangzhe.fun/rss.xml 探索、学习、进步、创造无限可能!
26 EXYONE@BLOG:~$ https://exyon.ee https://exyon.ee/feed.xml 一隅藏天地,流水遇知音。
27 小景 https://jingyuan-zheng.github.io/zh/ https://jingyuan-zheng.github.io/zh/index.xml 随心分享有趣的技术、开源项目与生活记录。
28 热汤茶馆 https://blog.hotsouprealm.top/ https://blog.hotsouprealm.top/atom.xml 茶凉了可以续,话断了可以接,这里是hahahotsoup的茶馆。
29 FGHRSH 日记簿 https://www.fghrsh.net/ https://www.fghrsh.net/feed.php 把喜欢的东西都记下来···
+46 -194
View File
@@ -2,358 +2,210 @@ repo_url
http://gdatura24gtdy23lxd7ht3xzx6mi7mdlkabpvuefhrjn4t5jduviw5ad.onion/Mayx/mayx
http://git.dkforestseeaaq2dqz2uflmlsybvnq2irzn4ygyvu53oazyorednviid.onion/mayx/blog
http://giteabolfdejtdzblkooalqei6jr67imiugmhtsh6ocw4hlj5a4q.b32.i2p/mayx/blog
https://lavaforge.org/mayx/blog
https://sij.ai/mayx/blog
https://repobase.net/mayx/blog
https://gitlab.lain.la/mayx/mayx.pages.lain.la
https://gitplac.si/mayx/mayx.gitpage.si
https://git.32bit.cafe/mayx/blog
https://gitnet.fr/mayx/blog
https://opencommit.eu/mayx/blog
https://forge.fedoraproject.org/mabbs/blog
https://tilde.club/~mayx/git/blog.git/
https://git.envs.net/Mayx/mayx
https://tildegit.org/Mayx/mayx
https://git.tilde.town/mayx/blog
https://gitlab.haskell.org/mayx/mayx
https://lavaforge.org/mayx/blog
https://repo2.serv00.com/git/pub/Mayx/mayx/
https://sij.ai/mayx/blog
https://git.projectnightcrawler.dev/mayx/blog
https://codefloe.com/mayx/blog
https://git.pixie.town/mayx/blog
https://git.minetest.land/Mayx/blog
http://1.6.141.109:3000/mayx/blog
https://code.dsconce.space/mayx/blog
https://mygit.iexercice.com/mayx/blog
http://162.215.134.149:4000/mayx/blog
http://45.33.87.87/mayx/blog
https://gitea.belanjaparts.com/mayx/blog
http://185.208.225.190:3000/mayx/blog
https://git.Apture.io/mayx/blog
http://8.134.61.107:3000/mayx/blog
http://219.157.255.213:25311/mayx/blog
http://116.236.50.103:8789/mayx/blog
https://git.daoyoucloud.com/mayx/blog
https://repo.gusdya.net/mayx/blog
https://gitea.synapsetec.cn/mayx/blog
http://gitea.yunshanghub.com:8081/mayx/blog
http://113.177.27.200:2033/mayx/blog
http://152.69.204.151:3000/mayx/blog
https://gitea.ontoast.uk/mayx/blog
http://187.216.152.151:9999/mayx/blog
http://116.63.173.179:8001/mayx/blog
https://k0ki-dev.com/mayx/blog
https://git.furcom.org/mayx/blog
https://git.karma-riuk.com/mayx/blog
https://git.7o9o.net/mayx/blog
https://git.gupaoedu.cn/mayx/blog
https://git.7milch.com/mayx/blog
https://dreamplacesai.de/mayx/blog
https://git.lekai.info/mayx/blog
https://git.novaa.xyz/mayx/blog
https://gitea.sciotech.cn/mayx/blog
https://git.gloje-rinchen-dorjee-rinpoche-buddhist-monastery.org/mayx/blog
https://gitea.micro-stack.org/mayx/blog
http://106.54.211.95:3000/mayx/blog
http://172.172.102.93:3000/mayx/blog
http://223.108.157.174:3000/mayx/blog
http://120.24.249.56/mayx/blog
http://120.211.66.170:8418/mayx/blog
https://gitea.vilcap.com/mayx/blog
http://8.138.142.17:3000/mayx/blog
http://209.127.59.74:3000/mayx/blog
https://git.dotb.cloud/mayx/blog
http://121.41.2.71:3000/mayx/blog
https://git.imvictor.tech:2/mayx/blog
http://e19510c831.iok.la/mayx/blog
http://119.45.49.212:3000/mayx/blog
https://code.antopie.org/mayx/blog
https://git.serenetia.com/mayx/blog
https://code.wxk8.com/mayx/blog
http://fanlibo.i234.me:8418/mayx/blog
https://git.temporaryname.org/mayx/blog
https://Repo.gusdya.net/mayx/blog
https://git.flymiracle.com/mayx/blog
https://git.stormrain.cn/mayx/blog
https://git.deuxfleurs.fr/mayx/blog
https://www.simpra.org:3000/mayx/blog
https://www.arcbyte.dev/mayx/blog
https://scm.bcorex.e3labs.net/mayx/blog
https://gitea.tecamino.com/mayx/blog
https://git.tobiasweise.dev/mayx/blog
https://code.cif.su/mayx/blog
https://git.cynic.moe/mayx/blog
https://git.muellers-software.org/mayx/blog
https://repos.fbpx.io/mayx/blog
https://git.cavemanon.xyz/mayx/blog
http://svn.rivastudio.cn/mayx/blog
http://118.178.172.49:3000/mayx/blog
https://gitea.lasallesaintdenis.com/mayx/blog
http://8.130.135.159:3000/mayx/blog
https://git.suo0.com/mayx/blog
https://git.dshkabatur.ru/mayx/blog
http://47.103.78.70:3000/mayx/blog
http://47.100.208.160:51300/mayx/blog
http://120.210.80.160:3000/mayx/blog
http://194.5.152.156:3000/mayx/blog
http://58.65.162.118:3000/mayx/blog
https://git.arkon.solutions/mayx/blog
http://8.131.93.145:54082/mayx/blog
https://git.7af.ru/mayx/blog
https://gitea.yimoyuyan.cn/mayx/blog
http://221.203.14.217:3000/mayx/blog
https://dev.kiramtech.com/mayx/blog
https://git.f4e.lol/mayx/blog
http://72.61.229.93:4000/mayx/blog
https://lishan148.synology.me:3014/mayx/blog_cn
http://110.42.101.39:13000/mayx/blog_cn
http://39.101.74.135:5000/mayx/blog_cn
http://123.57.130.140:3000/mayx/blog_cn
http://118.24.46.223:3000/mayx/blog_cn
http://s3.v100.vip:31057/mayx/blog_cn
https://www.syq.im:2025/mayx/blog_cn
http://101.33.225.95:3000/mayx/blog_cn
http://45.55.138.82:3000/mayx/blog_cn
http://118.24.129.148:3000/mayx/blog_cn
http://120.48.141.82:3000/mayx/blog_cn
http://82.156.111.58:3000/mayx/blog_cn
https://git.cool2645.com/mayx/blog_cn
http://175.27.229.211:3000/mayx/blog_cn
https://git.yidaimingjvn.xyz/mayx/blog_cn
http://39.105.67.143:3000/mayx/blog_cn
http://82.156.89.21:3000/mayx/blog_cn
http://xujiesoft.vicp.net:3000/mayx/blog_cn
https://git.mingliqiye.com/mayx/blog_cn
http://119.29.194.155:8894/mayx/blog_cn
http://58.213.60.6:19000/mayx/blog_cn
http://47.92.113.131:3000/mayx/blog
http://gyc.myds.me:4000/mayx/blog
https://git.graveyard.sh/mayx/blog
https://git.nathanspackman.com/mayx/blog
http://git.mynas71.myds.me/mayx/blog
https://git.veraskolivna.net/mayx/blog
https://git.lifetop.net/mayx/blog
https://gitea.jnyuxia.com/mayx/blog
https://gitea.hello.faith/mayx/blog
https://git.violka-it.net/mayx/blog
https://git.danpeak.co.uk/mayx/blog
https://gitea.mynas-lechner.de/mayx/blog
https://git.esen.gay/mayx/blog
https://git.webtims.ru/mayx/blog
https://gitlab.iplusus.com/mayx/blog
https://git.miasma-os.com/mayx/blog
https://gitea.nacsity.cn/mayx/blog
https://git.zakum.cn/mayx/blog
http://47.109.103.110:9000/mayx/blog_cn
http://51.159.198.233:3000/mayx/blog
https://gitea.adriangonzalezbarbosa.eu/mayx/blog
https://git.legatus.ru/mayx/blog
http://47.104.241.192:19999/mayx/blog_cn
http://47.98.148.146:1026/mayx/blog_cn
http://119.96.62.56:3000/mayx/blog_cn
http://43.143.209.246:6300/mayx/blog_cn
http://39.100.183.95:88/mayx/blog_cn
http://www.xshideserver.com:3000/mayx/blog_cn
http://159.75.131.235:3001/mayx/blog_cn
http://1.117.67.95:3000/mayx/blog_cn
http://81.70.102.154/mayx/blog_cn
http://111.228.34.40:3000/mayx/blog_cn
http://36.133.248.69:3088/mayx/blog_cn
http://220.205.16.27:18081/mayx/blog_cn
http://183.204.60.122:10081/mayx/blog_cn
https://gitea.wuyuank.com/mayx/blog_cn
http://47.106.222.181:20511/mayx/blog_cn
https://gitea.myat4.com/mayx/blog_cn
http://118.24.161.24:3000/mayx/blog_cn
http://githanea.s.odn.cc/mayx/blog_cn
https://git.dev.chuweizi.com/mayx/blog
http://36.138.125.206:3000/mayx/blog_cn
https://silica.codes/mayx/blog
https://git.crystalyx.net/mayx/blog
https://gittea.dev/mayx/blog
https://code.infininov.com/mayx/blog
https://src.enterpriselinux.social/mayx/blog
https://gitea.grxe.io/mayx/blog
https://gitea.accept.dev.dbf.nl/mayx/blog
https://git.jhossain.online/mayx/blog
https://barhoum-lab.fr/mayx/blog
https://git.fedaya.net/mayx/blog
https://redev.lol/mayx/blog
https://papi.tkpups.com/mayx/blog
https://git.beyond-a-i.org/mayx/blog
http://huanghomenas2.myqnapcloud.com:4000/mayx/blog_cn
http://git.zhmight.com/mayx/blog_cn
https://intl-dev.gaia888.com/mayx/blog_cn
http://gitea.snailtrack.cn/mayx/blog_cn
https://code.draussenfunker.de/mayx/blog_cn
https://git.dinsor.co.th/mayx/blog
http://www.arkproject.top/mayx/blog
http://www.bkandssp.cn:30/mayx/blog
http://gitea.ydxtool.com/mayx/blog
https://gitea.malxte.de/mayx/blog
https://git.0xee.eu/mayx/blog
https://git.crwlr.ir/mayx/blog
https://git.nozora.top/mayx/blog
https://git.edenit.co.kr/mayx/blog
https://git.catgirlsneed.homes/mayx/blog
http://47.108.255.216:3000/mayx/blog_cn
https://gitea.molietech.com/mayx/blog_cn
http://58.87.88.234:3000/mayx/blog_cn
http://210.75.240.13:3000/mayx/blog_cn
http://git.hbg99.com:8080/mayx/blog_cn
https://git.eplg.services/mayx/blog
http://namonba.asuscomm.com:3001/mayx/blog
http://109.199.98.226:3001/mayx/blog
https://gitea.digitanie.org/mayx/blog
https://git.xleed.com/mayx/blog
https://qlcodegitserver.online/mayx/blog
https://git.supernets.org/mayx/blog
https://git.digitaltelepresence.com/mayx/blog
https://git.hrfee.pw/mayx/blog
https://git.libregaming.org/mayx/blog
https://git.kaki87.net/mayx/blog
https://forgejo.vanten-s.com/mayx/blog
http://ysx.myds.me:3005/mayx/blog
http://124.71.169.183:3000/mayx/blog
https://git.csi-kjsce.org/mayx/blog
https://hiwifi.denq.us:8418/mayx/blog
https://git.msoucy.me/mayx/blog
http://140.120.108.238:49308/mayx/blog
http://git.chilidoginteractive.com:3000/mayx/blog_cn
https://git.thunder-data.cn/mayx/blog_cn
https://git.cukak.com/mayx/blog_cn
https://git.ixu.me/mayx/blog_cn
http://git.uhfdemo.com/mayx/blog_cn
http://43.143.142.38:7001/mayx/blog_cn
https://gitea.smartechouse.com/mayx/blog_cn
http://git.feashow.cn/mayx/blog_cn
http://git.yangben365.com/mayx/blog_cn
https://git.wieerwill.dev/mayx/blog
https://git.etwo.dev/mayx/blog
https://git.panda-number.one/mayx/blog
https://datacortex.pl/mayx/blog
https://git.trevorbotha.net/mayx/blog
https://viacivis.de/mayx/blog
http://www.wybpohro.com:3000/mayx/blog_cn
https://gitea.jobiglo.com/mayx/blog_cn
http://118.31.7.2:3000/mayx/blog_cn
http://www.dothing.com:6999/mayx/blog_cn
http://101.37.147.115:3000/mayx/blog_cn
https://gitea.originaltech.cn/mayx/blog_cn
https://git.akarmain.ru/mayx/blog_cn
https://git.georgew.dev/mayx/blog
https://gitea.keymantek.com:777/mayx/blog
http://git.realrobots.net/mayx/blog
https://www.tksea.top/mayx/blog
https://wtrb.uk:8443/mayx/blog
http://www.souper.ee/mayx/blog
https://git.tirtapakuan.co.id/mayx/blog
https://tacticallysolved.com/mayx/blog
https://git.juntekim.com/mayx/blog
https://git.farmtowntech.com/mayx/blog
https://gitea.viperlance.net/mayx/blog
https://git.woopwoopserver.com/mayx/blog
http://tian-you.top:7020/mayx/blog
http://34.17.182.140/mayx-gogs/blog
https://gitea.octifor.synology.me:60443/mayx/blog
http://gitea.mikarsoft.com/mayx/blog
https://git.zhewen-tong.cc/mayx/blog
https://mcgit.place/mayx/blog
https://git.sunfamily.site/mayx/blog
https://git.vgorl.is/mayx/blog
https://forgejo.rekallservices.com/mayx/blog
https://git.serradavid.fr/mayx/blog
http://git.instal.org.cn/mayx/blog
https://git.smart-family.net/mayx/blog
https://git.chiasson.cloud/mayx/blog
https://git.sleepeesoftware.fr/mayx/blog
https://git.everestlinux.org/mayx/blog
https://git.devinmajor.com/mayx/blog
https://git.netzbyte.com/mayx/blog
https://repo.saticogroup.com/mayx/blog
https://ataymakhzan.com/mayx/blog
https://repo.qruize.com/mayx/blog
https://git.xneon.org/mayx/blog
https://git.lamby.gay/mayx/blog
https://imperionblast.org/mayx/blog
https://donkey.abanbytes.eu/mayx/blog
https://lab.dutt.ch/mayx/blog
https://git.liestral.io/mayx/blog
https://gitea.dabit.synology.me/mayx/blog
http://www.javabrew.org/mayx/blog
https://git.joinplu.me/mayx/blog
http://otter.top/mayx/blog
https://git.xiongyi.xin/mayx/blog
https://git.pdy.ph/mayx/blog
http://188.66.0.25:3000/mayx/blog
https://gitea.deliverables.io/mayx/blog
https://git.sevenvm.de/mayx/blog
https://code.letsbe.solutions/mayx/blog
https://git.mlhub.it/mayx/blog
https://git.coastit.co.za/mayx/blog
https://git.0bin.in/mayx/blog
https://jsuse.com/mayx/blog
https://git.truncgil.com/mayx/blog
http://23.182.16.150:3000/mayx/blog
https://git.jingflowai.com/mayx/blog
https://git.ritonquilol.fr/mayx/blog
https://bis127.vse.cz/mayx/blog
https://gitea.diereimers.org/mayx/blog
https://app.gitpasha.com/mayx/blog
http://www.flowzl.top/mayx/blog
https://git.psg.net.au/mayx/blog
https://shaman2025.site/mayx/blog
https://gitea.ccllc.pro/mayx/blog
https://gitea.hpdocker.hpress.de/mayx/blog
https://vertpartneerss.club/mayx/blog
https://git.jhammah.com/mayx/blog
https://git.kemomimi.site/mayx/blog
https://gitea.kolesarhome.com/mayx/blog
http://sexy6688.vip/mayx/blog
https://gitbucket.aint-no.info/mayx/blog
https://gitea.emulab.it/mayx/blog
https://git.wikeo.ovh/mayx/blog
https://gitea.finesoft.club:4431/mayx/blog
https://gitea.precia.site/mayx/blog
https://git.bitscat.vn/mayx/blog
http://arch52.co.uk/mayx/blog
https://devcraft-dot.tech/mayx/blog
https://a-t-g.ru/mayx/blog
http://qiubei-git.cn/mayx/blog_cn
http://wangbeibei.xyz:6002/mayx/blog
http://8.141.82.163:20000/mayx/blog_cn
http://152.136.126.252:3000/mayx/blog_cn
https://home.zhupei.me:3000/mayx/blog_cn
http://gitea.coderpath.com/mayx/blog_cn
https://gitea.quiztimes.nl/mayx/blog_cn
https://www.chenisgod.com:3096/mayx/blog_cn
http://81.70.30.91:3000/mayx/blog_cn
http://yidaima.cn:6008/mayx/blog_cn
https://gitea.kdlsvps.top/mayx/blog_cn
http://8.148.31.14:3000/mayx/blog_cn
http://58.17.14.95:8001/mayx/blog_cn
http://39.99.175.172:8000/mayx/blog_cn
http://39.171.252.63:3000/mayx/blog_cn
https://git.lucas-michel.fr/mayx/blog_cn
https://git.archieri.fr/mayx/blog_cn
https://git.cdev.su/mayx/blog
https://root-kit.ru/mayx/blog
https://git.gnyra.com/mayx/blog_cn
https://git.newnaturalphilosophy.org/mayx/blog_cn
https://git.jingchengdl.com/mayx/blog_cn
https://gitea.detr.top/mayx/blog_cn
http://175.178.193.35:3000/mayx/blog_cn
http://frp5.mmszxc.xin:55469/mayx/blog_cn
http://gitea.jb1000.com:88/mayx/blog_cn
https://gitea.liyongzheng.fun/mayx/blog_cn
http://www.22ai.vip/mayx/blog_cn
https://git.tutulab.online/mayx/blog_cn
http://voodoo.plus/mayx/blog_cn
https://git.extra.eiffel.com/mayx/blog_cn
https://hdtime.space/mayx/blog_cn
http://219.151.177.172:3000/mayx/blog_cn
http://62.234.20.54:3000/mayx/blog_cn
https://git.urnas.cn/mayx/blog_cn
http://20.6.159.133:3000/mayx/blog_cn
https://gitea.4gunn.cn:52443/mayx/blog_cn
https://git.4tempo.com/mayx/blog_cn
https://gitea.nightdev.cn:9527/mayx/blog_cn
https://git.hivetasks.com/mayx/blog_cn
https://git.ddns.net/mayx/blog_cn
http://jdunlap.com/mayx/blog
https://git.silica.codes/mayx/blog
https://gitruhub.ru/mayx/blog
https://mjjtop.com/mayx/blog
https://forgejo.wanderingmonster.dev/mayx/blog
https://gitea.ns5001k.sigma2.no/mayx/blog
https://git.hemangvyas.com/mayx/blog
https://gitav.ru/mayx/blog
https://git.aptcloud.ru/mayx/blog
https://gitlab.dev.genai-team.ru/mayx/blog
https://git.umervtilte.lol/mayx/blog
https://git.bitpak.ru/mayx/blog
https://git.amamedis.de/mayx/blog
https://git.kunstglass.de/mayx/blog
https://git.kry008.xyz/mayx/blog
http://36.213.200.127:23000/mayx/blog_cn
https://git.niisse.net/mayx/blog_cn
https://gitea.yfandxt.asia/mayx/blog_cn
http://81.69.57.215:3000/mayx/blog_cn
https://git.dglyoo.com/mayx/blog_cn
http://c8202.top:3000/mayx/blog_cn
http://nojava.fun/mayx/blog_cn
https://git.vycsucre.gob.ve/mayx/blog
https://newtrafficworksgit.online/mayx/blog
https://git.uj-s.com/mayx/blog
https://git.albiobola.nl/mayx/blog
https://git.83channel.net/mayx/blog
https://repo.paperless.rs/mayx/blog
https://urbal.dev/mayx/blog
https://git.pwarde.nl/mayx/blog
https://bolha.dev/mayx/blog
https://git.checkerwars.com/mayx/blog
https://git.broderlands.com/mayx/blog
https://gitea.schwegmann.tech/mayx/blog
https://veretium.com/mayx/blog
https://gitea.kcm-nixi.cn:32443/mayx/blog
https://git.riskreduction.net/mayx/blog
https://git.twitchmagic.pro/mayx/blog
https://gitea.ctmp.ir/mayx/blog
https://gitea.marcin00.pl/mayx/blog
https://git.imagitech.mx/mayx/blog
https://git.hidosi.ru/mayx/blog
1 repo_url
2 http://gdatura24gtdy23lxd7ht3xzx6mi7mdlkabpvuefhrjn4t5jduviw5ad.onion/Mayx/mayx
3 http://git.dkforestseeaaq2dqz2uflmlsybvnq2irzn4ygyvu53oazyorednviid.onion/mayx/blog
4 http://giteabolfdejtdzblkooalqei6jr67imiugmhtsh6ocw4hlj5a4q.b32.i2p/mayx/blog
https://lavaforge.org/mayx/blog
https://sij.ai/mayx/blog
https://repobase.net/mayx/blog
5 https://gitlab.lain.la/mayx/mayx.pages.lain.la
6 https://gitplac.si/mayx/mayx.gitpage.si
7 https://git.32bit.cafe/mayx/blog
8 https://gitnet.fr/mayx/blog
9 https://opencommit.eu/mayx/blog
10 https://forge.fedoraproject.org/mabbs/blog
11 https://tilde.club/~mayx/git/blog.git/
12 https://git.envs.net/Mayx/mayx
13 https://tildegit.org/Mayx/mayx
14 https://git.tilde.town/mayx/blog
15 https://gitlab.haskell.org/mayx/mayx
16 https://lavaforge.org/mayx/blog
17 https://repo2.serv00.com/git/pub/Mayx/mayx/
18 https://sij.ai/mayx/blog
19 https://git.projectnightcrawler.dev/mayx/blog
20 https://codefloe.com/mayx/blog
21 https://git.pixie.town/mayx/blog
22 https://git.minetest.land/Mayx/blog
23 http://1.6.141.109:3000/mayx/blog
https://code.dsconce.space/mayx/blog
24 https://mygit.iexercice.com/mayx/blog
25 http://162.215.134.149:4000/mayx/blog
26 http://45.33.87.87/mayx/blog
https://gitea.belanjaparts.com/mayx/blog
27 http://185.208.225.190:3000/mayx/blog
28 https://git.Apture.io/mayx/blog
29 http://8.134.61.107:3000/mayx/blog
30 http://219.157.255.213:25311/mayx/blog
31 http://116.236.50.103:8789/mayx/blog
https://git.daoyoucloud.com/mayx/blog
32 https://repo.gusdya.net/mayx/blog
33 https://gitea.synapsetec.cn/mayx/blog
34 http://gitea.yunshanghub.com:8081/mayx/blog
http://113.177.27.200:2033/mayx/blog
35 http://152.69.204.151:3000/mayx/blog
https://gitea.ontoast.uk/mayx/blog
36 http://187.216.152.151:9999/mayx/blog
37 http://116.63.173.179:8001/mayx/blog
https://k0ki-dev.com/mayx/blog
38 https://git.furcom.org/mayx/blog
39 https://git.karma-riuk.com/mayx/blog
https://git.7o9o.net/mayx/blog
40 https://git.gupaoedu.cn/mayx/blog
41 https://git.7milch.com/mayx/blog
https://dreamplacesai.de/mayx/blog
https://git.lekai.info/mayx/blog
https://git.novaa.xyz/mayx/blog
https://gitea.sciotech.cn/mayx/blog
https://git.gloje-rinchen-dorjee-rinpoche-buddhist-monastery.org/mayx/blog
42 https://gitea.micro-stack.org/mayx/blog
43 http://106.54.211.95:3000/mayx/blog
44 http://172.172.102.93:3000/mayx/blog
http://223.108.157.174:3000/mayx/blog
http://120.24.249.56/mayx/blog
45 http://120.211.66.170:8418/mayx/blog
https://gitea.vilcap.com/mayx/blog
http://8.138.142.17:3000/mayx/blog
46 http://209.127.59.74:3000/mayx/blog
https://git.dotb.cloud/mayx/blog
47 http://121.41.2.71:3000/mayx/blog
https://git.imvictor.tech:2/mayx/blog
48 http://e19510c831.iok.la/mayx/blog
49 http://119.45.49.212:3000/mayx/blog
50 https://code.antopie.org/mayx/blog
51 https://git.serenetia.com/mayx/blog
https://code.wxk8.com/mayx/blog
http://fanlibo.i234.me:8418/mayx/blog
52 https://git.temporaryname.org/mayx/blog
53 https://Repo.gusdya.net/mayx/blog
https://git.flymiracle.com/mayx/blog
54 https://git.stormrain.cn/mayx/blog
55 https://git.deuxfleurs.fr/mayx/blog
56 https://www.simpra.org:3000/mayx/blog
https://www.arcbyte.dev/mayx/blog
https://scm.bcorex.e3labs.net/mayx/blog
https://gitea.tecamino.com/mayx/blog
57 https://git.tobiasweise.dev/mayx/blog
https://code.cif.su/mayx/blog
https://git.cynic.moe/mayx/blog
https://git.muellers-software.org/mayx/blog
https://repos.fbpx.io/mayx/blog
58 https://git.cavemanon.xyz/mayx/blog
59 http://svn.rivastudio.cn/mayx/blog
60 http://118.178.172.49:3000/mayx/blog
https://gitea.lasallesaintdenis.com/mayx/blog
61 http://8.130.135.159:3000/mayx/blog
https://git.suo0.com/mayx/blog
62 https://git.dshkabatur.ru/mayx/blog
63 http://47.103.78.70:3000/mayx/blog
http://47.100.208.160:51300/mayx/blog
http://120.210.80.160:3000/mayx/blog
64 http://194.5.152.156:3000/mayx/blog
http://58.65.162.118:3000/mayx/blog
65 https://git.arkon.solutions/mayx/blog
http://8.131.93.145:54082/mayx/blog
https://git.7af.ru/mayx/blog
66 https://gitea.yimoyuyan.cn/mayx/blog
67 http://221.203.14.217:3000/mayx/blog
https://dev.kiramtech.com/mayx/blog
68 https://git.f4e.lol/mayx/blog
69 http://72.61.229.93:4000/mayx/blog
https://lishan148.synology.me:3014/mayx/blog_cn
http://110.42.101.39:13000/mayx/blog_cn
70 http://39.101.74.135:5000/mayx/blog_cn
71 http://123.57.130.140:3000/mayx/blog_cn
72 http://118.24.46.223:3000/mayx/blog_cn
73 http://s3.v100.vip:31057/mayx/blog_cn
https://www.syq.im:2025/mayx/blog_cn
74 http://101.33.225.95:3000/mayx/blog_cn
75 http://45.55.138.82:3000/mayx/blog_cn
76 http://118.24.129.148:3000/mayx/blog_cn
77 http://120.48.141.82:3000/mayx/blog_cn
http://82.156.111.58:3000/mayx/blog_cn
https://git.cool2645.com/mayx/blog_cn
78 http://175.27.229.211:3000/mayx/blog_cn
https://git.yidaimingjvn.xyz/mayx/blog_cn
79 http://39.105.67.143:3000/mayx/blog_cn
80 http://82.156.89.21:3000/mayx/blog_cn
81 http://xujiesoft.vicp.net:3000/mayx/blog_cn
https://git.mingliqiye.com/mayx/blog_cn
http://119.29.194.155:8894/mayx/blog_cn
82 http://58.213.60.6:19000/mayx/blog_cn
83 http://47.92.113.131:3000/mayx/blog
84 http://gyc.myds.me:4000/mayx/blog
85 https://git.graveyard.sh/mayx/blog
https://git.nathanspackman.com/mayx/blog
http://git.mynas71.myds.me/mayx/blog
https://git.veraskolivna.net/mayx/blog
https://git.lifetop.net/mayx/blog
86 https://gitea.jnyuxia.com/mayx/blog
https://gitea.hello.faith/mayx/blog
https://git.violka-it.net/mayx/blog
https://git.danpeak.co.uk/mayx/blog
https://gitea.mynas-lechner.de/mayx/blog
https://git.esen.gay/mayx/blog
https://git.webtims.ru/mayx/blog
87 https://gitlab.iplusus.com/mayx/blog
88 https://git.miasma-os.com/mayx/blog
https://gitea.nacsity.cn/mayx/blog
https://git.zakum.cn/mayx/blog
89 http://47.109.103.110:9000/mayx/blog_cn
http://51.159.198.233:3000/mayx/blog
https://gitea.adriangonzalezbarbosa.eu/mayx/blog
https://git.legatus.ru/mayx/blog
90 http://47.104.241.192:19999/mayx/blog_cn
91 http://47.98.148.146:1026/mayx/blog_cn
http://119.96.62.56:3000/mayx/blog_cn
92 http://43.143.209.246:6300/mayx/blog_cn
93 http://39.100.183.95:88/mayx/blog_cn
http://www.xshideserver.com:3000/mayx/blog_cn
http://159.75.131.235:3001/mayx/blog_cn
http://1.117.67.95:3000/mayx/blog_cn
94 http://81.70.102.154/mayx/blog_cn
http://111.228.34.40:3000/mayx/blog_cn
95 http://36.133.248.69:3088/mayx/blog_cn
http://220.205.16.27:18081/mayx/blog_cn
96 http://183.204.60.122:10081/mayx/blog_cn
https://gitea.wuyuank.com/mayx/blog_cn
97 http://47.106.222.181:20511/mayx/blog_cn
https://gitea.myat4.com/mayx/blog_cn
98 http://118.24.161.24:3000/mayx/blog_cn
http://githanea.s.odn.cc/mayx/blog_cn
https://git.dev.chuweizi.com/mayx/blog
99 http://36.138.125.206:3000/mayx/blog_cn
https://silica.codes/mayx/blog
100 https://git.crystalyx.net/mayx/blog
https://gittea.dev/mayx/blog
https://code.infininov.com/mayx/blog
https://src.enterpriselinux.social/mayx/blog
https://gitea.grxe.io/mayx/blog
https://gitea.accept.dev.dbf.nl/mayx/blog
https://git.jhossain.online/mayx/blog
101 https://barhoum-lab.fr/mayx/blog
https://git.fedaya.net/mayx/blog
https://redev.lol/mayx/blog
https://papi.tkpups.com/mayx/blog
https://git.beyond-a-i.org/mayx/blog
http://huanghomenas2.myqnapcloud.com:4000/mayx/blog_cn
http://git.zhmight.com/mayx/blog_cn
https://intl-dev.gaia888.com/mayx/blog_cn
102 http://gitea.snailtrack.cn/mayx/blog_cn
103 https://code.draussenfunker.de/mayx/blog_cn
https://git.dinsor.co.th/mayx/blog
http://www.arkproject.top/mayx/blog
104 http://www.bkandssp.cn:30/mayx/blog
105 http://gitea.ydxtool.com/mayx/blog
106 https://gitea.malxte.de/mayx/blog
https://git.0xee.eu/mayx/blog
https://git.crwlr.ir/mayx/blog
107 https://git.nozora.top/mayx/blog
108 https://git.edenit.co.kr/mayx/blog
109 https://git.catgirlsneed.homes/mayx/blog
http://47.108.255.216:3000/mayx/blog_cn
https://gitea.molietech.com/mayx/blog_cn
http://58.87.88.234:3000/mayx/blog_cn
110 http://210.75.240.13:3000/mayx/blog_cn
111 http://git.hbg99.com:8080/mayx/blog_cn
112 https://git.eplg.services/mayx/blog
http://namonba.asuscomm.com:3001/mayx/blog
http://109.199.98.226:3001/mayx/blog
https://gitea.digitanie.org/mayx/blog
113 https://git.xleed.com/mayx/blog
https://qlcodegitserver.online/mayx/blog
114 https://git.supernets.org/mayx/blog
115 https://git.digitaltelepresence.com/mayx/blog
116 https://git.hrfee.pw/mayx/blog
117 https://git.libregaming.org/mayx/blog
118 https://git.kaki87.net/mayx/blog
119 https://forgejo.vanten-s.com/mayx/blog
http://ysx.myds.me:3005/mayx/blog
120 http://124.71.169.183:3000/mayx/blog
https://git.csi-kjsce.org/mayx/blog
https://hiwifi.denq.us:8418/mayx/blog
https://git.msoucy.me/mayx/blog
121 http://140.120.108.238:49308/mayx/blog
122 http://git.chilidoginteractive.com:3000/mayx/blog_cn
https://git.thunder-data.cn/mayx/blog_cn
https://git.cukak.com/mayx/blog_cn
https://git.ixu.me/mayx/blog_cn
http://git.uhfdemo.com/mayx/blog_cn
http://43.143.142.38:7001/mayx/blog_cn
https://gitea.smartechouse.com/mayx/blog_cn
http://git.feashow.cn/mayx/blog_cn
http://git.yangben365.com/mayx/blog_cn
https://git.wieerwill.dev/mayx/blog
https://git.etwo.dev/mayx/blog
123 https://git.panda-number.one/mayx/blog
124 https://datacortex.pl/mayx/blog
https://git.trevorbotha.net/mayx/blog
https://viacivis.de/mayx/blog
http://www.wybpohro.com:3000/mayx/blog_cn
https://gitea.jobiglo.com/mayx/blog_cn
http://118.31.7.2:3000/mayx/blog_cn
http://www.dothing.com:6999/mayx/blog_cn
http://101.37.147.115:3000/mayx/blog_cn
https://gitea.originaltech.cn/mayx/blog_cn
https://git.akarmain.ru/mayx/blog_cn
https://git.georgew.dev/mayx/blog
125 https://gitea.keymantek.com:777/mayx/blog
http://git.realrobots.net/mayx/blog
https://www.tksea.top/mayx/blog
https://wtrb.uk:8443/mayx/blog
http://www.souper.ee/mayx/blog
126 https://git.tirtapakuan.co.id/mayx/blog
https://tacticallysolved.com/mayx/blog
https://git.juntekim.com/mayx/blog
https://git.farmtowntech.com/mayx/blog
https://gitea.viperlance.net/mayx/blog
https://git.woopwoopserver.com/mayx/blog
127 http://tian-you.top:7020/mayx/blog
128 http://34.17.182.140/mayx-gogs/blog
https://gitea.octifor.synology.me:60443/mayx/blog
http://gitea.mikarsoft.com/mayx/blog
https://git.zhewen-tong.cc/mayx/blog
129 https://mcgit.place/mayx/blog
https://git.sunfamily.site/mayx/blog
https://git.vgorl.is/mayx/blog
130 https://forgejo.rekallservices.com/mayx/blog
https://git.serradavid.fr/mayx/blog
131 http://git.instal.org.cn/mayx/blog
132 https://git.smart-family.net/mayx/blog
133 https://git.chiasson.cloud/mayx/blog
https://git.sleepeesoftware.fr/mayx/blog
134 https://git.everestlinux.org/mayx/blog
https://git.devinmajor.com/mayx/blog
135 https://git.netzbyte.com/mayx/blog
https://repo.saticogroup.com/mayx/blog
136 https://ataymakhzan.com/mayx/blog
137 https://repo.qruize.com/mayx/blog
138 https://git.xneon.org/mayx/blog
https://git.lamby.gay/mayx/blog
https://imperionblast.org/mayx/blog
https://donkey.abanbytes.eu/mayx/blog
139 https://lab.dutt.ch/mayx/blog
https://git.liestral.io/mayx/blog
140 https://gitea.dabit.synology.me/mayx/blog
http://www.javabrew.org/mayx/blog
141 https://git.joinplu.me/mayx/blog
142 http://otter.top/mayx/blog
https://git.xiongyi.xin/mayx/blog
https://git.pdy.ph/mayx/blog
143 http://188.66.0.25:3000/mayx/blog
144 https://gitea.deliverables.io/mayx/blog
https://git.sevenvm.de/mayx/blog
https://code.letsbe.solutions/mayx/blog
https://git.mlhub.it/mayx/blog
https://git.coastit.co.za/mayx/blog
https://git.0bin.in/mayx/blog
https://jsuse.com/mayx/blog
https://git.truncgil.com/mayx/blog
145 http://23.182.16.150:3000/mayx/blog
https://git.jingflowai.com/mayx/blog
146 https://git.ritonquilol.fr/mayx/blog
147 https://bis127.vse.cz/mayx/blog
https://gitea.diereimers.org/mayx/blog
148 https://app.gitpasha.com/mayx/blog
http://www.flowzl.top/mayx/blog
https://git.psg.net.au/mayx/blog
149 https://shaman2025.site/mayx/blog
https://gitea.ccllc.pro/mayx/blog
https://gitea.hpdocker.hpress.de/mayx/blog
https://vertpartneerss.club/mayx/blog
150 https://git.jhammah.com/mayx/blog
151 https://git.kemomimi.site/mayx/blog
152 https://gitea.kolesarhome.com/mayx/blog
http://sexy6688.vip/mayx/blog
153 https://gitbucket.aint-no.info/mayx/blog
https://gitea.emulab.it/mayx/blog
154 https://git.wikeo.ovh/mayx/blog
https://gitea.finesoft.club:4431/mayx/blog
https://gitea.precia.site/mayx/blog
https://git.bitscat.vn/mayx/blog
http://arch52.co.uk/mayx/blog
155 https://devcraft-dot.tech/mayx/blog
156 https://a-t-g.ru/mayx/blog
http://qiubei-git.cn/mayx/blog_cn
157 http://wangbeibei.xyz:6002/mayx/blog
158 http://8.141.82.163:20000/mayx/blog_cn
http://152.136.126.252:3000/mayx/blog_cn
https://home.zhupei.me:3000/mayx/blog_cn
http://gitea.coderpath.com/mayx/blog_cn
https://gitea.quiztimes.nl/mayx/blog_cn
https://www.chenisgod.com:3096/mayx/blog_cn
http://81.70.30.91:3000/mayx/blog_cn
159 http://yidaima.cn:6008/mayx/blog_cn
https://gitea.kdlsvps.top/mayx/blog_cn
160 http://8.148.31.14:3000/mayx/blog_cn
161 http://58.17.14.95:8001/mayx/blog_cn
162 http://39.99.175.172:8000/mayx/blog_cn
http://39.171.252.63:3000/mayx/blog_cn
https://git.lucas-michel.fr/mayx/blog_cn
https://git.archieri.fr/mayx/blog_cn
https://git.cdev.su/mayx/blog
https://root-kit.ru/mayx/blog
https://git.gnyra.com/mayx/blog_cn
https://git.newnaturalphilosophy.org/mayx/blog_cn
https://git.jingchengdl.com/mayx/blog_cn
https://gitea.detr.top/mayx/blog_cn
163 http://175.178.193.35:3000/mayx/blog_cn
http://frp5.mmszxc.xin:55469/mayx/blog_cn
164 http://gitea.jb1000.com:88/mayx/blog_cn
https://gitea.liyongzheng.fun/mayx/blog_cn
165 http://www.22ai.vip/mayx/blog_cn
https://git.tutulab.online/mayx/blog_cn
http://voodoo.plus/mayx/blog_cn
166 https://git.extra.eiffel.com/mayx/blog_cn
https://hdtime.space/mayx/blog_cn
167 http://219.151.177.172:3000/mayx/blog_cn
168 http://62.234.20.54:3000/mayx/blog_cn
https://git.urnas.cn/mayx/blog_cn
http://20.6.159.133:3000/mayx/blog_cn
169 https://gitea.4gunn.cn:52443/mayx/blog_cn
https://git.4tempo.com/mayx/blog_cn
https://gitea.nightdev.cn:9527/mayx/blog_cn
https://git.hivetasks.com/mayx/blog_cn
https://git.ddns.net/mayx/blog_cn
170 http://jdunlap.com/mayx/blog
171 https://git.silica.codes/mayx/blog
172 https://gitruhub.ru/mayx/blog
173 https://mjjtop.com/mayx/blog
174 https://forgejo.wanderingmonster.dev/mayx/blog
175 https://gitea.ns5001k.sigma2.no/mayx/blog
176 https://git.hemangvyas.com/mayx/blog
177 https://gitav.ru/mayx/blog
178 https://git.aptcloud.ru/mayx/blog
179 https://gitlab.dev.genai-team.ru/mayx/blog
180 https://git.umervtilte.lol/mayx/blog
181 https://git.bitpak.ru/mayx/blog
182 https://git.amamedis.de/mayx/blog
183 https://git.kunstglass.de/mayx/blog
184 https://git.kry008.xyz/mayx/blog
185 http://36.213.200.127:23000/mayx/blog_cn
186 https://git.niisse.net/mayx/blog_cn
187 https://gitea.yfandxt.asia/mayx/blog_cn
188 http://81.69.57.215:3000/mayx/blog_cn
189 https://git.dglyoo.com/mayx/blog_cn
190 http://c8202.top:3000/mayx/blog_cn
191 http://nojava.fun/mayx/blog_cn
192 https://git.vycsucre.gob.ve/mayx/blog
193 https://newtrafficworksgit.online/mayx/blog
194 https://git.uj-s.com/mayx/blog
195 https://git.albiobola.nl/mayx/blog
196 https://git.83channel.net/mayx/blog
197 https://repo.paperless.rs/mayx/blog
198 https://urbal.dev/mayx/blog
199 https://git.pwarde.nl/mayx/blog
200 https://bolha.dev/mayx/blog
201 https://git.checkerwars.com/mayx/blog
202 https://git.broderlands.com/mayx/blog
203 https://gitea.schwegmann.tech/mayx/blog
204 https://veretium.com/mayx/blog
205 https://gitea.kcm-nixi.cn:32443/mayx/blog
206 https://git.riskreduction.net/mayx/blog
207 https://git.twitchmagic.pro/mayx/blog
208 https://gitea.ctmp.ir/mayx/blog
209 https://gitea.marcin00.pl/mayx/blog
210 https://git.imagitech.mx/mayx/blog
211 https://git.hidosi.ru/mayx/blog
-7
View File
@@ -1,6 +1,5 @@
proxies:
- https://blog.mayx.workers.dev/
- https://mayx.deno.dev/
- https://mayx.val.run/
- https://mayx.azion.app/
- https://yuki.gear.host/
@@ -16,16 +15,11 @@ mirrors:
- https://mayx.gitpage.si/
- https://mayx.serv00.net/
- https://mayx.vercel.app/
- https://mayx-blog.pgs.sh/
- https://mayx.netlify.app/
- https://mayx.gitnet.page/
- https://mayx.stormkit.dev/
- https://mayx.grebedoc.dev/
- https://mabbs.kinsta.page/
- https://mayx.codeberg.page/
- https://mayx.tildepages.org/
- https://mayx.pandastack.app/
- https://mayx.pages.lain.la/
- https://mayx.4everland.app/
- https://mayx.readthedocs.io/
- https://mayx.sourceforge.io/
@@ -42,7 +36,6 @@ repos:
- https://framagit.org/mayx/mayx.frama.io
- https://salsa.debian.org/mayx/mayx.pages.debian.net
- https://codeberg.org/mayx/blog
- https://pagure.io/mayx
- https://git.gay/mayx/mayx
- https://repo.or.cz/mayx.git
- https://gitea.com/mayx/mayx
+3 -1
View File
@@ -98,7 +98,8 @@ layout: xslt_container
<li><a href="{{ site.github.tar_url }}">Download <strong>TAR Ball</strong></a></li>
<li><a href="{{ site.github.repository_url }}">View On <strong>GitHub</strong></a></li>
{% else %}
<li style="width: 270px; border-right: none;"><a href="/MayxBlog.tgz">Download <strong>TGZ File</strong></a></li>
<li style="width: 174px;"><a href="/MayxBlog.7z">Download <strong>7-Zip File</strong></a></li>
<li style="width: 174px;"><a href="/offline.html">Manage <strong>Offline Mode</strong></a></li>
{% endif %}
</ul>
</header>
@@ -117,6 +118,7 @@ layout: xslt_container
<script src="{{ "/assets/js/scale.fix.js" | relative_url }}"></script>
<!--[if !IE]> -->
<script src="/assets/js/main_new.js"></script>
<script src="/assets/js/blog-console.js"></script>
<script src="/Live2dHistoire/live2d/js/live2d.js"></script>
<script src="/Live2dHistoire/live2d/js/message.js"></script>
<script src="/assets/js/jquery.pjax.min.js"></script>
+1 -1
View File
@@ -20,7 +20,7 @@ tags: [Mayx,MOS]
MOS是一个用来解释我不能解释的东西的一个代替品,就如同UFO和人们定义的神一样,因为未知,而又需要一种解释的方法,于是出现了MOS。
***
## 正文
![MOS_DEMO](/images/MOS_Demo.png "MOS的大概效果图")
![MOS_DEMO](/images/MOS_Demo.webp "MOS的大概效果图")
```
MOS Log System has been Load.
MOS Analysis System is Ready.
+1 -1
View File
@@ -139,7 +139,7 @@ Tel +34 612 520330
然后还非常用心的配了一张快递单:
![GEDSC AIRWAY BILL TO CHINA](/images/GEDSC%20AIRWAY%20BILL%20TO%20CHINA.jpg)
![GEDSC AIRWAY BILL TO CHINA](/images/GEDSC%20AIRWAY%20BILL%20TO%20CHINA.webp)
注:由于隐私原因,涉及隐私的地方已经被删除 ~~(尽管大家都知道我的手机号)~~
这个邮件当时也没细看,然后就给Lori Robinson说了我已经给他们发了我的信息,过了几天,她给我发:
+31
View File
@@ -0,0 +1,31 @@
---
layout: post
title: 一次制作终端版博客的经历
tags: [WASM, 终端, Linux, 博客]
---
在浏览器上用真的Linux访问博客会是什么样的感觉?<!--more-->
# 起因
在去年的时候我为了想在博客上加个终端,写过一篇关于[在浏览器中运行Linux](/2025/12/01/linux.html)的文章。不过那时候只是调查了有哪些可能的方法,并没有真的做出来,主要是自己做感觉太麻烦了😂。
但从上次进行了[Vibe Coding](/2026/07/01/vibe-coding.html)之后,我意识到这可能不再是什么麻烦事,也许只要和AI聊聊天就能轻松做出来了,于是接着之前的方案,我开始着手完成我的想法。
# 终端版博客的不同形式
## 基于WASM Linux的版本
在之前的文章里,我其实更倾向于WASM Linux的方案,尤其是[Thomas Stokes](https://github.com/tombl)制作的[WASM Linux](https://github.com/tombl/linux),因为它相比于虚拟机更轻量,效率更高,而且还能完美地在Safari中运行(毕竟我用的是macOS😂),因此接下来我计划用它为基础制作我的终端版博客。
不过之前我也说过,目前这个WASM Linux还不能和外界通信,不能和外界通信就不能读取我的文章。于是我打算先让AI解决这个问题。但如果我直接给它说想要和外界通信也许它会给我整一堆奇怪的实现,所以我得自己先想一个实现起来最简单的方案。之前我记得Fabrice Bellard大神写的JSLinux中有一个剪贴板设备文件可以和外部通信,那么干脆让AI写一个能在浏览器中执行JS的设备文件吧,这样做出来只需要写简单的Shell脚本就可以和浏览器交互了,也不需要考虑如何编译WASM程序,于是就这样我开始让它实现这个功能。
最终经过几次调试和修改之后AI真的把这个设备文件给做出来了,不过刚做出来的时候有些问题。因为在这个Linux中的进程都是在Web Worker中运行,执行的JS也会在Web Worker中运行。但是Web Worker的局限性很大,并不能和当前网页进行交互,不过这种问题也都是小事,发现了之后只要给AI一反馈也是轻轻松松就改好了……最终实现的效果我感觉相当不错,既然如此,那我就把AI做的这个驱动给上游[PR](https://github.com/tombl/linux/pull/24)一下吧,也算做个贡献。不过最终原作者不太喜欢这种实现方式,他好像更喜欢标准化的方式,说是已经做了一个[VsockDevice](https://github.com/tombl/linux/blob/b36416c7f266a465361ab7dde7622eb37239cd7e/tools/wasm/src/virtio.ts#L354),可以直接使用Linux自带的驱动和外界通信,所以把我的PR拒了(看来AI Slop还是入不了大佬的法眼😂)。 ~~不过我问了一下AI,它说用这个设备的话可能需要`ioctl()`系统调用,就没办法直接用Shell操作了,只能用别的语言写的程序才能用它通信,所以我只好自己用这种不标准的方式了。~~ 虽然VsockDevice确实是不太好操作,但是大佬随后又新增了一个ethernetDevice可以用来上网,考虑到维护自己的分支实在是太麻烦了,所以我让AI写了一个虚拟的HTTP服务,使其能够执行JS,顺便还增加了连接互联网的功能。这样我就可以直接使用上游的代码,不需要再维护之前写的驱动了(其实以太网比Vsock复杂多了,只是因为可以用wget之类的客户端请求所以才简单😋)。
在做好与外界通信的通道后我就开始让AI写能和我博客交互的脚本,不过在这个过程中也遇到了不少问题,最开始的时候发现这个Shell的管道有些问题,执行的时候会忽略管道前的命令,后面的命令就没法执行了……感觉有点奇怪,所以我要求它在实现交互脚本时不要使用管道符。除此之外我还发现反引号和`$()`这些能把执行结果插入字符串的语法也不能用……总之就是局限相当多,但它还是克服了这些困难最终把脚本给写出来了,最终实现了列出文章、查看文章、打开文章和查看评论等功能,效果还是相当不错。 ~~唯一的缺陷就是它不能进行异步操作,只能同步请求,所以在进行请求的时候会把浏览器卡住,体验稍有打折。~~ 后来用了网络版的JS执行服务后就可以异步了,避免出现把浏览器卡住的情况。
不过这个系统如果管道符不能用的话效果会差一大截,这可是Shell的灵魂啊,所以我提交了一个[Issue](https://github.com/tombl/linux/issues/25),希望作者能修一下。结果作者真的强得离谱,提出问题之后第二天就修复了,实在是太强了😨。但修好这个问题之后作者又整了一堆别的花活,把内存限制给取消了,结果我在Safari上一运行就报OOM的错误……于是我把这个问题又[反馈](https://github.com/tombl/linux/pull/26#issuecomment-5018155963)了一下,顺便让AI看看这个问题怎么解决,结果AI也不辜负我的期待,轻轻松松就解决了🤣。最终我除了会提出问题之外什么也不会做,只能看着大佬和AI完成我的想法了😝。
后来增加的以太网网关也挺折腾,原理其实并不复杂,但直接给AI说想用这个接口上网的时候它会自己想办法搓一套漏洞百出的TCP/IP协议栈,所以后来我在GitHub找了个[tcpip.js](https://github.com/chipmk/tcpip.js)库让它集成,BUG就比它自己手搓的少了不少。后来我又想加一个TLS终止功能,因为在浏览器上没办法直接让系统里的程序发送原始的TLS数据包去请求网站,但我又希望它能够请求带有https协议的网址,所以要提前对TLS进行解密再转换成`fetch()`,这时候我就没让AI自己手搓了,在网上找了个[node-forge](https://github.com/digitalbazaar/forge)库让AI对接,就可以避免出现一堆奇怪的BUG了。
做好网关之后我又突发奇想,想做一个展示终端内状态的页面,但是目前JS并不能主动和里面的系统直接通信,所以又让AI搓了一个通过Service Worker访问系统内网络服务的功能,然后用busybox的httpd执行sh写的cgi程序,就可以被前端读取到了。随后我还给原作者发了个[Issue](https://github.com/tombl/linux/issues/34)展示这个Service Worker的效果,结果人家已经做了这个功能,我还是太Naive了😂。
最终做好的网站可以在[这里](https://mabbs.github.io/linux/)查看,效果真的不是一般的好,相比于其他模仿终端形式的博客简直是降维打击,毕竟这是真的Linux啊~虽然目前依然有些BUG,但是原作者修BUG的速度还是挺快的,相信等他发布正式版之后,体验会更进一步。
## 基于JSNix的版本
虽然我觉得追求真实感很重要,但是我又回去看了一下为我提供想法的[JSNix](https://github.com/Erzbir/jsnix)。虽然这个项目并不是真的Linux,但是我让AI看了一遍之后它对这个项目的评价也很高,说是作者对Linux的了解非常深入,很多细节模拟的也非常到位。而且为WASM Linux制作的那些脚本,在这个JSNix上其实完全可以实现,还更简单,于是我直接让AI参考给WASM Linux写的脚本把那些功能全部移植到这个JSNix上。也许是因为AI更擅长编写网页,很快它就完成了这些功能,而且效果非常不错,基本上和WASM Linux上面的效果一样,表现也相当不错,最终生成的结果可以在[这里](https://mabbs.github.io/jsnix/)查看。
不过假的毕竟还是假的,和真的Linux差异还是有不少的,像正经的Shell可以在终端中直接用if、for和while之类的语法写一些东西,它这个就必须要编写成脚本才可以,而且解析也有相当多的问题。各种命令的运行结果很多都是硬编码的,只是编了一段对应命令的输出,参数做得也不全。另外著名的Linux文本三剑客grep、sed和awk在这个系统里实现得也非常差,只做了一些非常基础的功能,基本上就是完全不可用。所以说这个系统还是不够真实,只能做最基础的体验,不能够深究。
但并不是说它没有优点,它作为纯粹的JS不像WASM Linux那样容易出现兼容性问题,比如很多手机浏览器不支持跨源隔离,导致SharedArrayBuffer之类的API没法用,所以手机上只能体验这个JSNix的版本;另外WASM Linux的体积虽然说并不算很大,但也有好几MiB,不像这个JSNix连1MiB都不到;还有这个系统没有启动过程,相比于WASM Linux来说加载更快;而且在编写和修改功能的时候比WASM Linux简单不少,像给WASM Linux做的终端状态展示在JSNix上根本不需要折腾什么Service Worker,以及请求网络之类的也非常简单,直接`fetch()`就可以(也正是如此,JSNix的版本AI没一会儿就做完了)……总之就是只要不涉及具体功能方面JSNix都比WASM Linux要强不少,不过这样说有点“抛开事实不谈”的感觉了😂。
# 感想
最终做出来的这两个版本的终端版博客基本上所有的代码都不是我写的,真的做到了仅仅是和AI聊聊天就把东西做出来了……只能说AI现在真的不是一般的强啊~当然方案还是得自己考虑,因为AI一旦走错了方向就很容易往一个方向不停地钻,结果就是花了很长时间可能效果会很差,尤其是现在的AI还特别喜欢自己造轮子,所以这时候还是自己找找看已经有的方案然后告诉它会比较好。
从效果来说这次写的东西也让我非常满意,我想我这次做的这个博客界面估计是前所未有的,毕竟这可是在浏览器上用**真的**Linux访问博客啊,其他人做的都是假的。至于虚拟机那些方案可能也是因为太重,所以基本上没人用,而WASM Linux只有Thomas Stokes做的效果最好,但是又没有受到应有的关注……总之从我个人的角度来看这次做的东西还挺Cool,就是不知道会不会吸引其他人来看呢……可能还是太小众了吧😂。
+63
View File
@@ -0,0 +1,63 @@
---
layout: post
title: Vibe Coding 新体验(2)
tags: [Vibe Coding, AI, Agent]
---
AI is Everything!<!--more-->
# 起因
在两个月前,我[体验了现在的Vibe Coding](/2026/07/01/vibe-coding.html),不过那时候我其实还是没有完全信任AI,认为它的能力可能还不够强,大多数内容依然需要人来做。
但是自从上次做了[终端版博客](/2026/08/01/terminal.html)之后,我意识到,它的能力也许比我想象的更强,可能真的能做到超越99%的开发者了,因此在最近一段时间,我一直使用AI做我想做的各种东西,结果就是一次又一次地让我感到难以置信,现在的AI真的实在是太强了😨。而且我做的这一切并不是用最先进的那批AI,仅仅是国内被大家不怎么看好的免费模型——[腾讯混元Hy3](https://huggingface.co/tencent/Hy3),那么我很难想象,如果我能用像GPT 5.6 Sol或者Fable 5那样当下最先进的AI,我做的这些东西得有多完美啊……
# 最近Vibe Coding出来的各种项目
## Blog API + WebMCP
在上次做完[终端版博客](/2026/08/01/terminal.html)之后,我就在想,用Linux终端来呈现博客的形式真的是个好选择吗?毕竟Linux在浏览器上并不是原生的,尽管用了[WASM Linux](https://github.com/tombl/linux)可以做到WASM原生运行Linux,但这还是额外添加的东西。于是我就想,JavaScript应该也能作为终端吧?就像Node.js一样,REPL也可以作为终端使用,正好那段时间DeepSeek-V4-Flash正式版出了,就让它直接写了[一个Demo](https://github.com/Mabbs/jsnix/blob/main/repl.html)出来试试看,可以点开[这里](https://mabbs.github.io/jsnix/repl.html)体验。
写出来效果还挺不错的,和之前做的终端版博客的主要区别就是我写的是函数,而不是命令,不管怎么说也算是“真的”终端吧,唯一体验不太好的可能就是函数需要带个括号才能运行,稍微有点麻烦。
但后来我想了想,明明开发者工具中就有个控制台,根本没必要搞什么REPL终端啊,直接把要用的功能提出来作为函数,在控制台中运行效果比那个终端的效果要好得多啊,而且这样就可以直接集成到我的博客中,不需要单独搞个页面,感觉体验更好一些。
既然有了函数,我觉得只给人类用就太浪费了,前段时间看到谷歌搞了个[WebMCP](https://webmachinelearning.github.io/webmcp/),那我完全可以把这些函数做成WebMCP让AI来调用啊,相比于其他乱七八糟给AI的协议,至少WebMCP是有谷歌和微软撑腰的,所以加一个也不错。
最终我把WebMCP的文档以及之前实现的REPL代码直接发给AI,让它帮我实现对应的函数以及应用WebMCP API,没几分钟它就把所有功能实现好了,最终代码就是:[blog-console.js](/assets/js/blog-console.js)。在开发的过程中,AI严格按照文档中的要求实现了,结果我在实际测试的时候,发现Chrome在实现`registerTool`方法的时候和文档的实现有差异,文档说这个方法会返回Promise,但Chrome实际并没有返回,所以AI调用`.then()`时直接报错了😓……没办法就只能手动给它套一层Promise,才算是能正常工作。毕竟这还是一个没有正式上线的API,出点问题倒也能理解吧。
虽然东西已经实现了,不过到底怎么才能让其他人知道我用了WebMCP的技术呢?我发现网上有一个WebMCP Directory专门收录使用了WebMCP技术的网站,于是我就随手提交了一下,现在在[这里](https://webmcp.com/sites/mabbs.github.io)就能看到我博客的WebMCP信息了。
## 某个即将关服的游戏的离线快照
最近一段时间我在玩的一个使用Unity3D引擎制作的立直麻将游戏发了篇公告,说是要关服了,还给全服所有玩家送了一堆抽卡资源。我听到这个消息之后感觉还是挺遗憾的,那个游戏总体来说做得还算不错吧,而且画风也符合我的口味,角色模型有点米哈游的那种感觉。我曾经遇到过几次游戏关服的事件,但是每一次我都无能为力,只能看着游戏消失然后逐渐被遗忘。然而现在我有了AI,也许一切会变得不一样吧?这次我要使用AI,让即将消失的游戏不再消失。
### 重放大厅数据
最开始我其实想得很简单,游戏登录之后其实没有和服务器有太多的通信,像账号中有的角色以及各种材料等信息在登录的时候就已经发送了,直到进入对局之前客户端都没有和服务器有什么有效的沟通,而我主要需要的也就是能登进去看看角色,听听语音就好了。那么接下来我只需要让AI把整个游戏资源下载下来,然后抓个包,让它给客户端重放就好了,也不需要什么逆向操作。
AI分析了一下之后发现它用的是WebSocket+Protobuf协议,正常来说如果要使用Protobuf协议得要有.proto接口定义文件才可以,不过既然我的要求只是重放,它就没去考虑逆向了,只是简单分析了一下编码规则,就用Python把服务端给写出来了。
不过如果这个快照必须本地启动一个服务器,玩起来就有点麻烦了,这样搞得我估计就没心思玩它了,但是自从我做了终端版博客之后,我觉得浏览器可能没有做不到的事情,既然Python能运行服务器,那JavaScript一样可以,于是我就要求AI让游戏和服务端一起运行在浏览器中。
结果它还真折腾出来了,在JavaScript中似乎一切都可以被覆盖,于是AI就替换了浏览器的WebSocket对象,让客户端以为它连接到了真实的服务器,然后再按照之前录制的包发送就好了,折腾了一段时间之后游戏真的能正常启动了,真的是太离谱了😨。
### 制作对局引擎
其实这基本上已经满足我的要求了,但我还是有点好奇,这个客户端里真的没有.proto接口定义文件吗?正常来说既然客户端知道怎么和服务端通信,那么这个文件应该是必须要有的吧?反正目的已经达到了,接下来就让AI瞎折腾看看。
在我要求完之后它又折腾了好长时间,结果.proto也被它给搞出来了😨,也许对于会逆向Unity3D游戏的人来说这不算什么,但这已经让我很震惊了……既然有了这些,也许完成一个完整的服务端将不再是难事。
于是我让它录制了几把我实际对局的包,并理解整个对局中数据包的时序,开始考虑如何做出完整的麻将对局。首先作为离线运行的游戏来说,和人对局肯定是做不出来了,那就得想办法搓个人机出来。我最先想到的是著名的麻将AI [Mortal](https://github.com/Equim-chan/Mortal),之前用它在雀魂上打了几把之后结果号没了😂,不过我确实觉得和人打麻将没什么意思,感觉麻将如果全按最优解打的话结果就全是靠运气……不过后来我想了一下还是不用Mortal了,首先这个模型不小,整个模型要好几百MiB,而且三麻和四麻的模型还不一样,还有三麻的模型还是收费的……另外真和Mortal打的话感觉难度也确实有点高,除了运气因素外,至少人机犯错的概率比人要低不少,那打起来估计会很难受。所以我就对AI说,做一个降低向听数为主的全攻型人机,其他细节让它自己考虑,另外我想到上次做的那个麻将Wordle它自己写的役种分析实在是有点烂,所以这次就告诉它必须要使用现成的[Riichi](https://github.com/takayama-lily/riichi)库,免得出乱七八糟的问题。
在连续跑了好几个小时之后AI成功把完整的[对局引擎](https://github.com/Mabbs/mahjong-dream-snapshot/blob/main/mock/riichi.js)给搓出来了,真的是相当了不起,要知道这个东西正常来说人开发怎么说也得好几周吧?它就几个小时开发出来了,还理解了数据包的时序,用之前分析的.proto文件成功接入到了游戏中,实在是太强了……当然因为我没有用大量真实牌谱去测试,有BUG也是正常的,比如目前的引擎还存在立直之后完全没法开杠这样的边缘问题,就只能慢慢修了,不过这都是小问题,它能做到目前的水平已经不是一般人能做到的了。
最终做出来的项目可以在[这里](https://github.com/Mabbs/mahjong-dream-snapshot)看到,还可以通过[GitHub Pages](https://mabbs.github.io/mahjong-dream-snapshot/)上的部署直接游玩。
### 隐藏彩蛋
在AI做完整个服务端之后,我闲得没事瞎改对战人机的角色和皮肤,因为游戏中的皮肤ID有一定的规律,所以可以根据角色ID来猜皮肤ID。在这个时候,我发现其中有一个角色在游戏中没有对应的泳装皮肤,但是我把人机的角色按照规律配置推测的泳装皮肤ID是能显示出来的……那也就是说这个游戏可能在配置中隐藏了这个皮肤,但是并没有删掉美术资源,所以接下来我就想着能不能让AI把这个隐藏的皮肤显示出来,这样就能在游戏中直接使用了。
所以我根据自己推测的信息,告诉AI:在这个游戏中有一个皮肤编号3708002,头像编号1018的资源被隐藏了,其中关于这些资源的配置应该在commonconfigs中,帮我解包并分析,考虑如何将这个皮肤和头像恢复显示,然后重新打包资源,使其可以正常加载。就这么个需求,它从Bundle的解包,到分析FlatBuffers,然后又处理XAsset的校验……最终花了3个小时就给我做出来了,而且我试了一下,非常完美,简直就像这个皮肤本来就存在一样,不过原本这就是被官方隐藏的嘛,解除隐藏自然就和真的存在一样啊😂。
## 能在WASM Linux中运行的SSG
之前我做完终端版博客之后就在想,让这个完整的Linux只用来展示博客似乎有点浪费,感觉它还能做更多事,但对我来说编译在这个系统中能运行的二进制程序还是有一点困难,虽然我有用AI,但是我的Agent是运行在macOS上的,所以编译Linux二进制程序有点困难……不过单是BusyBox其实就能做相当多的事情了,毕竟我当年可是在手机上用ash做出了论坛系统[Mabbs](https://github.com/Mabbs/Mabbs.project),那它能做的事情应该还有很多。
最近我在和群友聊天的时候发现有人用C#做了个静态网站生成器,也是用AI做的,看起来挺有意思。其实SSG的原理还是挺简单的,说到底就是些文字转换罢了。于是我就想,要不然我就让AI搓个能在WASM Linux中运行的SSG出来吧?
当然直接搓我还是会有一点怀疑AI的能力,所以又找了点素材,之前在[体验Tilde社区](/2025/08/10/tilde.html)的时候发现了[bashblog](https://github.com/cfenollosa/bashblog),其实这就已经是一个现成的SSG了,只不过它用的是Bash语法,得让AI转换成POSIX风格的语法。另外它还依赖使用Perl语言编写的[Markdown库](https://daringfireball.net/projects/markdown/),我这个系统里可没有Perl啊,也得想办法找个可以替代的东西。
我想了一下,在BusyBox中最适合处理文本的大概就是awk了,所以我首先应该找找有没有用awk编写的Markdown转换程序。结果搜了一下还真被我找到了,有一个叫做[d.awk](https://github.com/wernsey/d.awk)的库,里面包含一个完全用awk写的Markdown转换程序,那么接下来我只需要把这两个项目丢给AI,让它以bashblog为基础,以d.awk为引擎制作一个兼容性极强的POSIX SSG吧。
这种问题果然还是太简单了,它只用了一两个小时的时间,就真的做出来了,最终它把这个项目起名叫做[possg](https://gist.github.com/Mabbs/3db745dc403d752a1d59d3e06938e310),并且我把这个程序粘贴到了我的终端版博客中运行了一下,效果也是非常完美……如果哪一天我抛弃了Jekyll,也许就会用这个possg吧😂。
## 终端版博客的各种新功能
最近一段时间我在我的[终端版博客](https://github.com/Mabbs/linux)又有一些新的想法,于是就对着AI反复许愿,结果AI还真的能让愿望成真。以下就是从上次做完之后又新增的一些功能:
### Markdown高亮
最初博客在终端中显示文章时是直接打印的Markdown原文,感觉不太好看,所以我就想着如果能加个高亮功能体验应该会更好吧?最开始我要求AI做这个功能的时候它选择使用Shell+sed来实现,那时候我还没有做possg,没考虑用awk来做,所以性能非常差。因为Shell在处理这种任务的时候要不停地调用sed,这个系统开进程的开销又很大,所以处理一篇文章得好几秒,后来才考虑用awk来做,结果性能确实好了不少,最终的代码可以在[这里](https://github.com/Mabbs/linux/blob/initramfs/bin/mdhl)查看。
### 挂载本地文件夹
前段时间我看到作者合并了一个关于[virtiofs的PR](https://github.com/tombl/linux/pull/40),当时我看到这个PR的时候就想,记得Chrome浏览器有一个[showDirectoryPicker API](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/showDirectoryPicker),可以允许网页对指定的文件夹进行读写。现在有了这个驱动的话,让AI做个将本地文件夹挂载到Linux的功能应该是个很简单的事情,于是我直接就闭着眼睛让AI做了。
做完之后虽然能用,但是我又回想起上次让AI搓的通过Service Worker访问系统内网络服务的功能,结果人家原作者已经做了一样的功能,我还给作者发了[Issue](https://github.com/tombl/linux/issues/34)推荐😅,所以这次我得吸取教训看看作者是不是已经做了这个功能,毕竟作者实现virtiofs的时候估计就有这个打算吧?结果看了一眼还真[做了](https://github.com/tombl/distro/blob/main/packages/linux-guest/src/browser.ts)😆,我闭着眼睛让这个垃圾AI去做看来确实有点着急了,虽然我不知道原来我的AI做得到底怎么样,但是我对它还是没什么信心,也许作者这个代码也是AI写的,但很明显他的AI比我的AI水平强得多,所以让它参考作者做的这个版本重新写了一版。总之最终都能用,写出来的代码就是:[hostfs.js](https://github.com/Mabbs/linux/blob/main/hostfs.js),再配合上[mount_host](https://github.com/Mabbs/linux/blob/initramfs/bin/mount_host)脚本就可以实现挂载本地文件夹的功能了。
### 基于WebRTC的局域网内组网
我在制作网络模块的时候一直在想,有没有办法能在不依靠服务器的情况下让两个这样的Linux系统互联呢?正常来说要想让两个浏览器内的服务互相访问就必须有一个中转服务器,因为浏览器肯定是不允许网页监听端口,而且就算能监听,我的这个网页还是放在GitHub Pages上,使用了HTTPS,也没办法访问HTTP资源,搞证书也是个麻烦事。
但其实浏览器是有P2P功能的,那就是[WebRTC](https://developer.mozilla.org/zh-CN/docs/Web/API/WebRTC_API),只是我不太确定这个技术能不能做到无服务器,于是我问了问AI,这个功能能不能不要服务器,结果AI说可以,只要两个机器在同一个局域网,就可以通过手动交换SDP信令来实现P2P连接,而且考虑到我基本上完全没有锥形NAT网络可以用,所以就不搞STUN服务器了,反正也连不上。那既然理论上可以的话,就让AI实现吧。
最终花了一点时间AI就写出来了,使用[webrtc-p2p.js](https://github.com/Mabbs/linux/blob/main/webrtc-p2p.js)配合[connector](https://github.com/Mabbs/linux/blob/initramfs/bin/connector)就可以实现同一个局域网内两台电脑上的浏览器中的Linux互联了。
这个功能做出来之后直接就有了无限的想象空间,比如想用A电脑的浏览器控制B电脑浏览器中的Linux,就可以在B电脑中执行`tcpsvd -v 0.0.0.0 4444 /usr/bin/script -qc 'stty -echo; /bin/sh -i' /dev/null`,然后在A电脑用`nc 10.0.2.x 4444`去连接就可以了,效果就像SSH一样,而且配合之前做的挂载本地文件夹功能,甚至可以直接操作B电脑中的文件;另外之前做的Service Worker访问系统内网络服务也能用得上,假如B电脑有一个网页想要分享给A电脑,可以在B电脑上用`mount_host`把本地文件夹挂载到Linux的`/mnt/host`目录中,然后在那个目录执行`httpd -p 8080`,A电脑可以执行`tcpsvd -v 0.0.0.0 8080 /usr/bin/nc 10.0.2.x 8080`转发,接下来打开`http/8080/`就可以访问到B电脑上的网页了。
总之只要有了网络,这个系统的功能就能被无限扩大了,只是……说是这么说,其实还是依赖浏览器的功能,所以这些功能就算用JavaScript直接做也完全没问题,但毕竟是作为Linux终端,有了这些能力之后就显得更完整了。
最终我仔细确认了原作者确实没有做这个功能,所以就发了个[Issue](https://github.com/tombl/distro/issues/156)看看他有没有兴趣把这个功能加到他的库中,如果被接受了也算是做出贡献了吧。
### 使用VLESS协议的网关
虽然之前已经加了基于浏览器Fetch API的网关,但这个网关的局限性还是挺大的,基本上也就只能请求网站了。那其他能在浏览器上运行系统的项目是怎么做的呢?看了一下像v86用的是一个基于L2 Over WS的公共网关relay.widgetry.org,而WebVM用的则是Tailscale。这两种我都不喜欢,主要是这些东西基本上专门服务于这类在浏览器中运行的系统,普通人并不会特意去搭建,而我既不想用某个特定的服务,也不想自己搭……那么有什么样的网络服务既随处可见,又能在浏览器中使用呢?这时候我就想到了VLESS协议,它天生就适合运行在WebSocket中,而且搭建起来极其简单,比如有人用Cloudflare Workers做了一个叫做[edgetunnel](https://github.com/zizifn/edgetunnel)的脚本,可以轻松将Cloudflare作为代理使用,因此我决定接下来让AI实现一个基于VLESS+WSS的网关,这样除了请求网站之外还能做点别的事情,比如用`telnet towel.blinkenlights.nl`命令看ASCII版的《星球大战》😆。
只不过相比于其他项目用的那些L2的服务来说,VLESS承载的是L4的协议,所以我还得让AI实现TCP/IP协议栈……其实我不太想这样做的,因为AI做这种事情容易出错,但是它看了一下我之前用的[tcpip.js](https://github.com/chipmk/tcpip.js)库说是里面的API不满足要求……那没办法,AI既然自己想搓就让它搓吧。不过VLESS协议这个我觉得还是得给它个[文档](https://github.com/XTLS/Xray-docs-next/blob/main/docs/en/development/protocols/vless.md),免得它开始瞎编。
不管怎么说它还是搓出来了,虽然质量我不是特别满意……但毕竟手头用的AI不是最先进的AI,就先这样吧~最终的代码就是:[vless-gateway.js](https://github.com/Mabbs/linux/blob/main/vless-gateway.js),用法也很简单,首先要有一个**证书合法**的VLESS+WSS节点,然后在我的博客终端中按F12打开控制台,输入:`switchToVless("wss://domain:port/path", "UUID")`就可以将原来基于Fetch的网络切换到VLESS节点的网络了,考虑到避免被滥用的情况,我就不内置节点了,如果有人有兴趣可以用自己的节点进行连接。
# 感想
做到这里,可以感受到最近的AI所做的一切真的是令人难以置信,它不仅知道几乎所有知识,还有不知疲倦的思考能力,以及足够的动手能力,而这也仅仅建立在一个很一般的免费模型上。再回想起前段时间用的Agent,做的什么[订阅源预览](/2025/04/08/feed.html),简直就太简单了😂。目前据我所知其他人使用更先进的AI已经可以解决人类没能解决的数学难题、自主设计处理器指令集架构在上面运行Doom、[从零开发一个Classic Mac OS](https://github.com/Kelsidavis/System7)、制作直接在UEFI中运行的软件、以及完全逆向Unity3D游戏,把几款游戏混合起来做成一个新游戏……(至于网上总传的什么DeepSeek的所谓的灰测模型搞出来的[各种Demo](https://github.com/6WENHAO/DS-Games)就算了,根本比不了)过去各种难以想象的东西都被轻松做出来了,而这一切就发生在最近几个月,我已经很难想象未来AI能做到什么样的地步了,也许AI真的会带领人类走向无法想象的高度吧~
File diff suppressed because it is too large. Load diff
+714
View File
@@ -0,0 +1,714 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import argparse
import binascii
import lzma
import os
import struct
import sys
import time
# ============================================================
# Part 1: 最小 LZMA1 range coder(只做编码)
#
# 设计目标:只实现 quine 构造需要的 token:match / rep0-match,
# 编码字节与历史内容无关(不维护输出历史),
# 这是"先算结构、后填数据"两阶段装配的基础。
# 参考:Igor Pavlov 的 LZMA SDK (LzmaEnc.c / LzmaDec.c)。
# ============================================================
kNumBitModelTotalBits = 11
kBitModelTotal = 1 << kNumBitModelTotalBits # 2048
kNumMoveBits = 5
kTopValue = 1 << 24 # 0x1000000
kNumStates = 12
kNumLitStates = 7
kNumPosBitsMax = 4
kNumLenToPosStates = 4
kNumAlignBits = 4
kEndPosModelIndex = 14
kNumFullDistances = 1 << (kEndPosModelIndex >> 1) # 128
kMatchMinLen = 2
kNumLowLenBits = 3
kNumMidLenBits = 3
kNumHighLenBits = 8
kNumLowLenSymbols = 1 << kNumLowLenBits # 8
kNumMidLenSymbols = 1 << kNumMidLenBits # 8
kNumPosSlotBits = 6
PROB_INIT = kBitModelTotal >> 1 # 1024
class RangeEncoder:
"""LZMA 的区间编码器。low 是 64 位(要容纳进位),range 是 32 位。"""
def __init__(self):
self.low = 0
self.range = 0xFFFFFFFF
self.cache = 0
self.cache_size = 1 # 初值 1 -> 第一个输出字节恒为 0x00
self.buf = bytearray()
def _shift_low(self):
if (self.low & 0xFFFFFFFF) < 0xFF000000 or (self.low >> 32) != 0:
temp = self.cache
while True:
self.buf.append((temp + (self.low >> 32)) & 0xFF)
temp = 0xFF
self.cache_size -= 1
if self.cache_size == 0:
break
self.cache = (self.low >> 24) & 0xFF
self.cache_size += 1
# 注意:C 里 `(UInt32)low << 8` 会截断回 32 位,必须照做,
# 否则 low 的高位会一直累积,进位位 (low>>32) 就不止 0/1 了。
self.low = ((self.low & 0xFFFFFFFF) << 8) & 0xFFFFFFFF
def encode_bit(self, probs, idx, bit):
p = probs[idx]
bound = (self.range >> kNumBitModelTotalBits) * p
if bit == 0:
self.range = bound
probs[idx] = p + ((kBitModelTotal - p) >> kNumMoveBits)
else:
self.low += bound
self.range -= bound
probs[idx] = p - (p >> kNumMoveBits)
while self.range < kTopValue:
self.range = (self.range << 8) & 0xFFFFFFFF
self._shift_low()
def encode_direct_bits(self, value, num_bits):
for i in range(num_bits - 1, -1, -1):
self.range >>= 1
if (value >> i) & 1:
self.low += self.range
while self.range < kTopValue:
self.range = (self.range << 8) & 0xFFFFFFFF
self._shift_low()
def bittree_encode(self, probs, off, num_bits, symbol):
"""普通位树:MSB 优先,节点下标从 1 开始。"""
m = 1
for i in range(num_bits - 1, -1, -1):
bit = (symbol >> i) & 1
self.encode_bit(probs, off + m, bit)
m = (m << 1) | bit
def bittree_reverse_encode(self, probs, off, num_bits, symbol):
"""反向位树:LSB 优先,用于 SpecPos / Align。"""
m = 1
for i in range(num_bits):
bit = symbol & 1
symbol >>= 1
self.encode_bit(probs, off + m, bit)
m = (m << 1) | bit
def finish(self):
for _ in range(5):
self._shift_low()
return bytes(self.buf)
def _pos_slot_and_bits(d):
"""把 0 基距离 d (=真实距离-1) 拆成 (posSlot, 低位数, 低位值)。"""
if d < 4:
return d, 0, 0
# posSlot >= 4 时:numDirectBits=(slot>>1)-1, base=(2|(slot&1))<<numDirectBits
for slot in range(4, 64):
n = (slot >> 1) - 1
base = (2 | (slot & 1)) << n
if d < base + (1 << n):
return slot, n, d - base
raise ValueError("distance too large: %d" % d)
class LzmaEncoder:
def __init__(self, pb=2):
"""
不维护输出假历史:match 的编码字节只取决于 (dist,len,pos),与历史内容无关,
因此 quine 构造(先算结构、后填数据)不需要逐字节复制假历史。
"""
self.pb = pb
self.pos_mask = (1 << pb) - 1
self.rc = RangeEncoder()
self.pos = 0 # 已输出字节数
self.state = 0
# 概率模型
n = PROB_INIT
self.p_is_match = [n] * (kNumStates << kNumPosBitsMax)
self.p_is_rep = [n] * kNumStates
self.p_is_rep_g0 = [n] * kNumStates
self.p_rep0_long = [n] * (kNumStates << kNumPosBitsMax)
self.p_pos_slot = [n] * (kNumLenToPosStates << kNumPosSlotBits)
self.p_spec_pos = [n] * (kNumFullDistances - kEndPosModelIndex) # 114
self.p_align = [n] * (1 << kNumAlignBits)
self.p_len_choice = [n] * 2
self.p_len_low = [n] * (16 * kNumLowLenSymbols)
self.p_len_mid = [n] * (16 * kNumMidLenSymbols)
self.p_len_high = [n] * (1 << kNumHighLenBits)
self.p_rep_len_choice = [n] * 2
self.p_rep_len_low = [n] * (16 * kNumLowLenSymbols)
self.p_rep_len_mid = [n] * (16 * kNumMidLenSymbols)
self.p_rep_len_high = [n] * (1 << kNumHighLenBits)
# ---------- 内部 ----------
@property
def pos_state(self):
return self.pos & self.pos_mask
def _encode_len(self, choice, low, mid, high, length):
"""choice 是 2 元素数组:choice[0]=choice 位,choice[1]=choice2 位。"""
ps = self.pos_state
l = length - kMatchMinLen
if l < kNumLowLenSymbols:
self.rc.encode_bit(choice, 0, 0)
self.rc.bittree_encode(low, ps << kNumLowLenBits, kNumLowLenBits, l)
else:
self.rc.encode_bit(choice, 0, 1)
l -= kNumLowLenSymbols
if l < kNumMidLenSymbols:
self.rc.encode_bit(choice, 1, 0)
self.rc.bittree_encode(mid, ps << kNumMidLenBits, kNumMidLenBits, l)
else:
self.rc.encode_bit(choice, 1, 1)
self.rc.bittree_encode(high, 0, kNumHighLenBits, l - kNumMidLenSymbols)
# ---------- 对外 token ----------
def _write_dist(self, dist, lts):
d = dist - 1
slot, n, low_bits = _pos_slot_and_bits(d)
self.rc.bittree_encode(self.p_pos_slot, lts << kNumPosSlotBits,
kNumPosSlotBits, slot)
if slot >= 4:
if slot < kEndPosModelIndex:
base = (2 | (slot & 1)) << n
off = base - slot - 1
self.rc.bittree_reverse_encode(self.p_spec_pos, off, n, low_bits)
else:
self.rc.encode_direct_bits(low_bits >> kNumAlignBits, n - kNumAlignBits)
self.rc.bittree_reverse_encode(self.p_align, 0, kNumAlignBits,
low_bits & ((1 << kNumAlignBits) - 1))
def match(self, dist, length):
assert kMatchMinLen <= length <= 273, length
ps = self.pos_state
self.rc.encode_bit(self.p_is_match, (self.state << kNumPosBitsMax) + ps, 1)
self.rc.encode_bit(self.p_is_rep, self.state, 0) # 非 rep
lts = min(length - kMatchMinLen, kNumLenToPosStates - 1)
self._encode_len(self.p_len_choice,
self.p_len_low, self.p_len_mid, self.p_len_high, length)
self._write_dist(dist, lts)
# 状态更新
self.state = 7 if self.state < kNumLitStates else 10
self.pos += length
def rep_match(self, length):
"""rep0 匹配:只使用最近一次 match 的距离(quine 构造只需要这种)。"""
assert kMatchMinLen <= length <= 273, length
ps = self.pos_state
self.rc.encode_bit(self.p_is_match, (self.state << kNumPosBitsMax) + ps, 1)
self.rc.encode_bit(self.p_is_rep, self.state, 1)
self.rc.encode_bit(self.p_is_rep_g0, self.state, 0)
self.rc.encode_bit(self.p_rep0_long, (self.state << kNumPosBitsMax) + ps, 1)
self._encode_len(self.p_rep_len_choice,
self.p_rep_len_low, self.p_rep_len_mid, self.p_rep_len_high, length)
self.state = 8 if self.state < kNumLitStates else 11
self.pos += length
def finish(self):
return self.rc.finish()
# ============================================================
# Part 2: 7z 格式原语
# ============================================================
SIG = b"7z\xbc\xaf'\x1c"
VER = b"\x00\x04"
MAX_MATCH = 273 # LZMA1 单个 match 长度上限
# FILETIME 纪元(1601-01-01)与 Unix 纪元(1970-01-01)之差,单位 100ns
FILETIME_EPOCH_OFFSET = 116444736000000000
def crc32(b, v=0):
return binascii.crc32(b, v) & 0xFFFFFFFF
def varint(v):
"""7z UINT64 变长编码:首字节高位连续 n 个 1 + 值的 (7-n) 个高位,再跟 n 字节 LE。"""
if v < 0x80:
return bytes([v])
for n in range(1, 9):
if v < (1 << (8 * n + 7 - n)):
break
else:
raise ValueError("varint too big")
first = ((0xFF << (8 - n)) & 0xFF) | ((v >> (8 * n)) & ((1 << (7 - n)) - 1))
return bytes([first]) + (v & ((1 << (8 * n)) - 1)).to_bytes(n, "little")
def store_hdr(payload_len, first=False):
"""LZMA2 uncompressed chunk 头(3 字节,大端 size-1)。"""
assert 1 <= payload_len <= 65536
return bytes([0x01 if first else 0x02]) + (payload_len - 1).to_bytes(2, "big")
def matches_for(dist, total):
"""dist 固定、总长 total 的 token 序列:首个 match + 后续 rep0。
长度按 273 上限切分,并避开"只剩 1 字节"(rep0 最小长度为 2)。"""
assert total >= 2
toks = []
first = min(MAX_MATCH, total)
if total - first == 1:
first -= 1
toks.append(("m", dist, first))
total -= first
while total > 0:
l = min(MAX_MATCH, total)
if total - l == 1:
l -= 1
toks.append(("r0", l))
total -= l
return toks
# ============================================================
# Part 3: 多文件 7z quine 构造器
# ============================================================
PROPS_BYTE = 0x5D # lc=3, lp=0, pb=2
CHUNK = 65536 # LZMA2 单 chunk 解压上限
SEED_NOTE = ("\nMayx's Blog!").encode("utf-8")
def dict_prop_for(maxdist):
"""选最小的 LZMA2 dict prop 使字典 >= maxdist。"""
for p in range(41):
if (2 | (p & 1)) << (p // 2 + 11) >= maxdist:
return p, (2 | (p & 1)) << (p // 2 + 11)
raise ValueError("distance too large")
def lzma_chunk(tokens, out_pos):
"""编一个 LZMA chunk(0xC0: state+props reset,无 dict reset,无 end marker)。
match token 的编码字节只取决于 (dist,len,pos),与历史内容无关(不维护输出历史)。"""
enc = LzmaEncoder()
enc.pos = out_pos
total = 0
for t in tokens:
if t[0] == "m":
enc.match(t[1], t[2])
total += t[2]
else:
enc.rep_match(t[1])
total += t[1]
data = enc.finish()
assert total - 1 < 65536 and len(data) - 1 < 65536
hdr = bytes([0xC0]) + (total - 1).to_bytes(2, "big") + \
(len(data) - 1).to_bytes(2, "big") + bytes([PROPS_BYTE])
return hdr + data, total
class BlogQuine:
def __init__(self, root, quine_name="MayxBlog.7z", seed_name="_quine_seed.bin",
src_dir=""):
self.quine_name = quine_name
self.seed_name = seed_name
# 源文件在归档内的前缀目录(如 "src" → 归档内出现 src/hello.txt);"" = 根目录
self.src_dir = src_dir.strip("/")
# 程序运行时间 → FILETIME(1601-01-01 起 100ns 计数),
# 本次运行只取一次,作为归档内所有文件条目的修改时间字段。
self.mtime_ft = time.time_ns() // 100 + FILETIME_EPOCH_OFFSET
# walk_entries: walk 顺序(父先子后、同级 dirs 在前)的 (rel, is_dir)。
# 子流顺序 = 非空条目在此列表中的顺序,必须自始至终保持同一顺序。
self.walk_entries = []
self.files = [] # (relpath, data bytes),顺序 = walk_entries 中文件序
# src_dir 的各级父目录先作为目录条目挂入(父先子后),
# 这样归档结构完整:如 src_dir="public/src" → "public"、"public/src"
if self.src_dir:
parts = self.src_dir.split("/")
for i in range(1, len(parts) + 1):
self.walk_entries.append(("/".join(parts[:i]), True))
for dirpath, dirnames, filenames in os.walk(root):
dirnames.sort()
for dn in dirnames:
rel = os.path.relpath(os.path.join(dirpath, dn), root).replace(os.sep, "/")
self.walk_entries.append((self._prefixed(rel), True))
for fn in sorted(filenames):
p = os.path.join(dirpath, fn)
rel = os.path.relpath(p, root).replace(os.sep, "/")
with open(p, "rb") as fp:
data = fp.read()
self.walk_entries.append((self._prefixed(rel), False))
self.files.append((self._prefixed(rel), data))
self.dirs = [r for r, isd in self.walk_entries if isd]
self.content = b"".join(data for _, data in self.files)
def _prefixed(self, rel):
return self.src_dir + "/" + rel if self.src_dir else rel
def entries(self):
ent = [{"name": self.seed_name, "dir": False}]
ent += [{"name": r, "dir": isd} for r, isd in self.walk_entries]
ent.append({"name": self.quine_name, "dir": False})
return ent
# ---------- 7z header ----------
def build_header(self, n, total, d, entries, sub_sizes):
n_sub = len(sub_sizes)
N = len(entries)
h = bytearray()
h += b"\x01\x04" # kHeader, kMainStreamsInfo
h += b"\x06" + varint(0) + varint(1) + b"\x09" + varint(n) + b"\x00" # PackInfo
h += b"\x07" # UnpackInfo
h += b"\x0b" + varint(1) + b"\x00" # kFolder: 1, local
h += varint(1) + b"\x21\x21" + varint(1) + bytes([self.dict_prop])
h += b"\x0c" + varint(d + total) # kCodersUnpackSize
h += b"\x00" # end UnpackInfo
h += b"\x08" # SubStreamsInfo
h += b"\x0d" + varint(n_sub) # 子流数
h += b"\x09" + b"".join(varint(s) for s in sub_sizes[:-1]) # 前 n_sub-1 个大小
h += b"\x0a" + b"\x01" # kCRC: AllDefined
crc_base = len(h)
h += b"\x00" * (4 * n_sub) # CRC 槽(quine 的待定点)
h += b"\x00\x00" # end SSI, end MainStreamsInfo
h += b"\x05" + varint(N) # FilesInfo
bits = bytearray((N + 7) // 8) # kEmptyStream 位域(MSB 优先)
for i, e in enumerate(entries):
if e["dir"]:
bits[i // 8] |= 1 << (7 - i % 8)
h += b"\x0e" + varint(len(bits)) + bytes(bits)
# kMTime:所有条目(文件与目录)写入修改时间(= 本次程序运行时间)
mt = b"\x01\x00" + struct.pack("<Q", self.mtime_ft) * N
h += b"\x14" + varint(len(mt)) + mt
names = bytearray(b"\x00") # kName: external=0
for e in entries:
names += e["name"].encode("utf-16-le") + b"\x00\x00"
h += b"\x11" + varint(len(names)) + bytes(names)
attrs = b"".join(struct.pack("<I", 0x10 if e["dir"] else 0x20)
for e in entries)
h += b"\x15" + varint(2 + len(attrs)) + b"\x01\x00" + attrs # kAttributes
h += b"\x00\x00" # end FilesInfo, end Header
return bytes(h), crc_base
# ---------- 结构计算 ----------
def layout(self, d_seed, maxdist_hint):
content_len = len(self.content)
d = d_seed + content_len
# C1 链:k 个 uncompressed chunk,载荷总长 d+35
Ls = []
rem = d + 35
while rem > CHUNK:
Ls.append(CHUNK)
rem -= CHUNK
Ls.append(rem)
assert Ls[-1] >= 2, "末 chunk 载荷 %d 太小,调整 d_seed" % Ls[-1]
k = len(Ls)
hdrA = store_hdr(CHUNK, first=False) # 0x02 FF FF
hdrB = store_hdr(Ls[-1], first=False) # 末 chunk 头(可能与 A 相同)
self.dict_prop, dict_size = dict_prop_for(maxdist_hint)
# 子流大小(quine 的 total 在迭代后填):seed, 各文件, quine
file_sizes = [len(data) for _, data in self.files]
T, h = 100, 100
for _ in range(30):
chunks = []
o, f = 0, 32
S = 0
for j, L in enumerate(Ls): # C1a..C1k
chunks.append({"kind": "store", "foff": f, "size": 3 + L,
"ooff": o, "olen": L, "first": j == 0,
"c1": True, "S": S})
o += L
f += 3 + L
S += L
assert o == d + 35
# repro:复现 file[35 : 32+3k+d+35) = pay0 hdr1 pay1 ... hdr(k-1) pay(k-1)
S = 0
for j, L in enumerate(Ls):
if j > 0:
# chunk j 的 3 字节头:从 seed 开头样本复制
srcpos = 0 if (j < k - 1 or L == CHUNK) else 3
dist_h = (d + 35 + S + 3 * (j - 1)) - srcpos
hb, hu = lzma_chunk([("m", dist_h, 3)], o)
assert hu == 3
chunks.append({"kind": "lzma", "foff": f, "size": len(hb),
"ooff": o, "olen": hu, "bytes": hb})
o += hu
f += len(hb)
# chunk j 的载荷:从 W[S:S+L) 复制
cb, cu = lzma_chunk(matches_for(d + 35 + 3 * j, L), o)
assert cu == L
chunks.append({"kind": "lzma", "foff": f, "size": len(cb),
"ooff": o, "olen": cu, "bytes": cb})
o += cu
f += len(cb)
S += L
# gears: store(x)+copy(x) 把 slip 压到 <=16
while f - (o - d) > 16:
s = f - (o - d)
x = min(s + 3, CHUNK)
chunks.append({"kind": "store", "foff": f, "size": 3 + x,
"ooff": o, "olen": x})
o += x
f += 3 + x
cb, cu = lzma_chunk(matches_for(x, x), o)
chunks.append({"kind": "lzma", "foff": f, "size": len(cb),
"ooff": o, "olen": cu, "bytes": cb})
o += cu
f += len(cb)
# jump: 单 match 从 W[6:6+y)(seed 里的种植串)复制,使 slip = -3
s = f - (o - d)
y = s + 17
for _ in range(10):
jb, ju = lzma_chunk([("m", o - 6, y)], o)
y2 = s + 3 + len(jb)
if y2 == y:
break
y = y2
assert ju == y and y2 == y, "jump 不动点失败"
assert y + 6 <= d_seed, "种植串 %d+6 超过 seed %d" % (y, d_seed)
chunks.append({"kind": "lzma", "foff": f, "size": len(jb),
"ooff": o, "olen": ju, "bytes": jb, "jump": True})
o += ju
f += len(jb)
# gadget: 真空 store(T) + match(T,T) + 结束标记
assert f == (o - d) - 3, "slip=%d 应为 -3" % (f - (o - d))
chunks.append({"kind": "store", "foff": f, "size": 3 + T,
"ooff": o, "olen": T, "vac": True})
f += 3 + T
o += T
gb, gu = lzma_chunk(matches_for(T, T), o)
assert gu == T
chunks.append({"kind": "lzma", "foff": f, "size": len(gb),
"ooff": o, "olen": gu, "bytes": gb, "gmatch": True})
f += len(gb)
o += gu
# LZMA2 流结束标记(0x00):7z t 需要它确认流完整结束
chunks.append({"kind": "term", "foff": f, "size": 1,
"ooff": o, "olen": 0, "bytes": b"\x00"})
f += 1
n = f - 32
total = f + h
sub_sizes = [d_seed] + file_sizes + [total]
entries = self.entries()
hdr, crc_base = self.build_header(n, total, d, entries, sub_sizes)
h_new = len(hdr)
T_new = len(gb) + 1 + h_new
if T_new == T and h_new == h:
assert o == d + f + h, "输出终点 %d != d+total %d" % (o, d + f + h)
return {"d": d, "d_seed": d_seed, "k": k, "Ls": Ls,
"chunks": chunks, "header": hdr, "crc_base": crc_base,
"n": n, "total": f + h, "T": T, "h": h,
"hdrA": hdrA, "hdrB": hdrB, "jump_y": y,
"n_sub": len(sub_sizes), "sub_sizes": sub_sizes,
"entries": entries, "dict_size": dict_size}
T, h = T_new, h_new
raise RuntimeError("layout 不收敛")
# ---------- 装配 ----------
def assemble(self, lay):
F = bytearray(lay["total"])
d, h = lay["d"], lay["h"]
d_seed = lay["d_seed"]
hoff = lay["total"] - h
# pass A: sig + 所有非 payload 字节
F[0:6] = SIG
F[6:8] = VER
struct.pack_into("<Q", F, 12, lay["n"]) # NextHeaderOffset
struct.pack_into("<Q", F, 20, h) # NextHeaderSize
jump_c = vac_c = None
for c in lay["chunks"]:
if c["kind"] == "store":
F[c["foff"]:c["foff"] + 3] = store_hdr(c["olen"],
first=c.get("first", False))
if c.get("vac"):
vac_c = c
else:
F[c["foff"]:c["foff"] + c["size"]] = c["bytes"]
if c.get("jump"):
jump_c = c
F[hoff:hoff + h] = lay["header"]
assert jump_c and vac_c
# pass B: seed = hdrA ‖ hdrB ‖ 种植串 ‖ 填充
y = lay["jump_y"]
plant = bytes(F[jump_c["ooff"] - d: jump_c["ooff"] - d + y])
seed = bytearray(d_seed)
seed[0:3] = lay["hdrA"]
seed[3:6] = lay["hdrB"]
seed[6:6 + y] = plant
pad = (SEED_NOTE * (d_seed // len(SEED_NOTE) + 2))[:d_seed - 6 - y]
seed[6 + y:] = pad
# pass C: 各 store 的 payload
P = bytes(seed) + self.content + bytes(F[0:35]) # = W[0:d+35)
assert len(P) == d + 35
for c in lay["chunks"]:
if c["kind"] != "store":
continue
ooff, olen = c["ooff"], c["olen"]
if c.get("c1"):
F[c["foff"] + 3:c["foff"] + 3 + olen] = P[c["S"]:c["S"] + olen]
elif c.get("vac"):
fo = ooff - d
F[fo:fo + olen] = F[fo + olen:fo + 2 * olen]
else:
fo = ooff - d
F[c["foff"] + 3:c["foff"] + 3 + olen] = F[fo:fo + olen]
return F, bytes(seed)
# ---------- CRC 定点(96x96 GF(2)) ----------
def solve_crc(self, F, lay, seed):
T, h, d = lay["T"], lay["h"], lay["d"]
k, n_sub, crc_base = lay["k"], lay["n_sub"], lay["crc_base"]
hoff = lay["total"] - h
vout_f = lay["total"] - 2 * T
hcopy = vout_f + (T - h)
# 已知 CRC:seed + 各文件(quine 的是未知量 D)
known = [crc32(seed)] + [crc32(data) for _, data in self.files]
assert len(known) == n_sub - 1
for i, e in enumerate(known):
struct.pack_into("<I", F, hoff + crc_base + 4 * i, e)
struct.pack_into("<I", F, hcopy + crc_base + 4 * i, e)
# 未知量 D N S,各出现两处
dpos = crc_base + 4 * (n_sub - 1)
ncopy = 32 + 3 * k + d # C1k 载荷里 F[0:35) 副本起点
groups = [[hoff + dpos, hcopy + dpos],
[28, ncopy + 28],
[8, ncopy + 8]]
Fv = memoryview(F)
def targets():
return (crc32(Fv),
crc32(Fv[hoff:hoff + h]),
crc32(Fv[12:32]))
base = targets()
basevec = base[0] | (base[1] << 32) | (base[2] << 64)
cols = []
for g in range(3):
for kb in range(32):
poss = [p + kb // 8 for p in groups[g]]
bb = kb % 8
for p in poss:
F[p] ^= (1 << bb)
t = targets()
for p in poss:
F[p] ^= (1 << bb)
cols.append((t[0] ^ base[0]) | ((t[1] ^ base[1]) << 32)
| ((t[2] ^ base[2]) << 64))
# (I+A) x = basevec;方程 i: x_i ^ sum_j A[i][j] x_j = base_i
eqs = []
for i in range(96):
coeff = 0
for j in range(96):
if (cols[j] >> i) & 1:
coeff |= 1 << j
coeff ^= 1 << i # 对角元 = 1 ^ A[i][i]
eqs.append([coeff, (basevec >> i) & 1])
for j in range(96):
p = next((i for i in range(j, 96) if (eqs[i][0] >> j) & 1), None)
if p is None:
raise RuntimeError("GF(2) 奇异 @bit%d" % j)
eqs[j], eqs[p] = eqs[p], eqs[j]
for i in range(96):
if i != j and ((eqs[i][0] >> j) & 1):
eqs[i][0] ^= eqs[j][0]
eqs[i][1] ^= eqs[j][1]
x = 0
for i in range(96):
assert eqs[i][0] == (1 << i)
if eqs[i][1]:
x |= 1 << i
vals = [x & 0xFFFFFFFF, (x >> 32) & 0xFFFFFFFF, (x >> 64) & 0xFFFFFFFF]
for g in range(3):
for p in groups[g]:
F[p:p + 4] = struct.pack("<I", vals[g])
t = targets()
assert t == tuple(vals), "CRC 定点失败: %s != %s" % (t, vals)
return vals
def build(self, maxdist_hint=None):
if maxdist_hint is None:
# 最大距离 ~ jump 的 dist ≈ 2d + repro/gear 余量
maxdist_hint = 2 * (len(self.content) + 128) + (1 << 21)
d_seed = 64
last_err = None
for _ in range(80):
d = d_seed + len(self.content)
if (d + 35) % CHUNK == 1: # L_k = 1 无法编 match
d_seed += 1
continue
try:
lay = self.layout(d_seed, maxdist_hint)
F, seed = self.assemble(lay)
crcs = self.solve_crc(F, lay, seed)
return bytes(F), lay, seed, crcs
except (RuntimeError, AssertionError) as e:
last_err = e
d_seed += 1
raise RuntimeError("build 多次失败: %s" % last_err)
def verify(F, lay, seed, files):
"""解码 LZMA2 流并逐文件校验。"""
d, d_seed = lay["d"], lay["d_seed"]
dec = lzma.LZMADecompressor(
format=lzma.FORMAT_RAW,
filters=[{"id": lzma.FILTER_LZMA2, "dict_size": lay["dict_size"]}])
W = dec.decompress(F[32:32 + lay["n"]])
assert len(W) == d + len(F), "|W|=%d 期望 %d" % (len(W), d + len(F))
ok = True
if W[:d_seed] != seed:
print("[verify] seed 不匹配")
ok = False
pos = d_seed
for rel, data in files:
if W[pos:pos + len(data)] != data:
print("[verify] 文件不匹配:", rel)
ok = False
pos += len(data)
assert pos == d
if W[d:] != F:
print("[verify] quine 自复制不匹配")
ok = False
print("[verify] 自解码: |W|=%d, seed/文件/quine 全部匹配: %s" % (len(W), ok))
return ok
def main():
ap = argparse.ArgumentParser(description="多文件 7z quine 生成器")
ap.add_argument("srcdir")
ap.add_argument("output")
ap.add_argument("--quine-name", default=None,
help="归档内 quine 文件名(默认 = output 的文件名)")
ap.add_argument("--seed-name", default=".this_is_mayx_blog")
ap.add_argument("--src-dir", default="",
help="源文件在归档内的前缀目录(如 src;默认根目录)")
ap.add_argument("--quine-dir", default="",
help="quine 在归档内所在的目录(如 public;默认根目录)")
args = ap.parse_args()
quine_name = args.quine_name or os.path.basename(args.output)
if args.quine_dir:
# 归档内路径用 / 分隔(与 walk 收集的目录条目一致)
quine_dir = args.quine_dir.strip("/")
quine_name = quine_dir + "/" + quine_name
bq = BlogQuine(args.srcdir, quine_name=quine_name, seed_name=args.seed_name,
src_dir=args.src_dir)
print("[blogquine] %d 个文件, %d 个目录, 内容 %d 字节"
% (len(bq.files), len(bq.dirs), len(bq.content)))
F, lay, seed, crcs = bq.build()
with open(args.output, "wb") as fp:
fp.write(F)
print("[blogquine] written %s: %d 字节 (d=%d, k=%d chunks, n=%d, h=%d, T=%d)"
% (args.output, len(F), lay["d"], lay["k"], lay["n"], lay["h"], lay["T"]))
print("[blogquine] CRC 定点: D(quine)=%08x N(hdr)=%08x S(sig)=%08x" % tuple(crcs))
ok = verify(F, lay, seed, bq.files)
sys.exit(0 if ok else 1)
if __name__ == "__main__":
main()
+4 -4
View File
@@ -6,9 +6,9 @@ cd blog
mkdir Mabbs
curl -L -o Mabbs/README.md https://github.com/Mabbs/Mabbs/raw/main/README.md
bundle exec jekyll build -d ../public_html
rsync --delete -rv ../public_html/ mayx@pgs.sh:/blog
tar czvf MayxBlog.tgz --exclude-vcs ../public_html/
mv MayxBlog.tgz ../public_html/
python3 _tools/blogquine.py --src-dir blog --quine-dir blog ../public_html/ MayxBlog.7z
7z a -mx9 -bd -bb0 MayxBlog-new.7z MayxBlog.7z
mv MayxBlog-new.7z ../public_html/MayxBlog.7z
cd ../public_html/
unset GIT_DIR
git init
@@ -19,6 +19,6 @@ git branch pages
git remote add codeberg ssh://git@codeberg.org/mayx/pages.git
git remote add bitbucket ssh://git@bitbucket.org/unmayx/unmayx.bitbucket.io.git
git push -f codeberg main
git push -f codeberg pages
git push -f codeberg pages
git push -f bitbucket main
/home/mayx/blog-env/node_modules/surge/bin/surge /home/mayx/public_html/ mayx.surge.sh
+5 -2
View File
@@ -6,18 +6,21 @@ cd blog
mkdir Mabbs
curl -L -o Mabbs/README.md https://github.com/Mabbs/Mabbs/raw/main/README.md
bundle exec jekyll build -d ../domains/mayx.serv00.net/public_html/
tar czvf MayxBlog.tgz --exclude-vcs -C ../domains/mayx.serv00.net public_html/
mv MayxBlog.tgz ../domains/mayx.serv00.net/public_html/
python3 _tools/blogquine.py --src-dir blog --quine-dir blog ../domains/mayx.serv00.net/public_html/ MayxBlog.7z
7zz a -mx9 -bd -bb0 MayxBlog-new.7z MayxBlog.7z
mv MayxBlog-new.7z ../domains/mayx.serv00.net/public_html/MayxBlog.7z
rsync -avz --delete ../domains/mayx.serv00.net/public_html/ mayx@web.sourceforge.net:/home/project-web/mayx/htdocs/
cd ../domains/mayx.serv00.net/public_html/
unset GIT_DIR
git init
git lfs install
git lfs track "*.png"
git lfs track "*.webp"
git lfs track "*.moc"
git lfs track "*.tgz"
git lfs track "*.jpg"
git lfs track "*.zip"
git lfs track "*.7z"
git branch -m main
echo "---
title: Mayx
File diff suppressed because it is too large. Load diff
+119
View File
@@ -0,0 +1,119 @@
/*
* Mayx的博客 · /offline.html 控制页控制器(classic script,PJAX 兼容)
*
* 本体是 IIFE,不含模块语法:PJAX 换页时 jQuery 会重新执行本文件,
* 每次执行都重新查询元素、重新绑定事件,天然幂等。真正的解包逻辑在
* ES module /assets/js/offline-unpack.js 中,通过动态 import() 加载
* (浏览器模块缓存保证 PJAX 反复进入本页也不会重复加载)。
* 注意:本文件经 Liquid 原样输出,不能使用模板语法字符。
*/
(function () {
var MODULE_URL = '/assets/js/offline-unpack.js';
// 压缩包大致量级(当前约 27MB),仅用于进度条比例,不影响正确性
var EXPECTED_BYTES = 27 * 1024 * 1024;
var busy = false;
function el(id) { return document.getElementById(id); }
function fmtMB(bytes) { return (bytes / 1048576).toFixed(1) + ' MB'; }
function setMessage(text) {
el('offline-message').textContent = text || '';
}
function showProgress(show) {
el('offline-progress-box').hidden = !show;
}
function render(meta) {
var online = !meta;
el('offline-state').textContent = online
? '在线模式(离线未启用)'
: '离线模式(优先从本地缓存加载)';
el('offline-version').textContent = online ? '-' : fmtMB(meta.payloadSize) + '(压缩包大小即版本号)';
el('offline-files').textContent = online ? '-' : meta.files + ' 个文件 / ' + fmtMB(meta.bytes);
el('offline-time').textContent = online ? '-' : new Date(meta.unpackedAt).toLocaleString();
el('offline-persist').textContent = online ? '-' : (meta.persisted ? '已授权(缓存不会被自动回收)' : '未授权(浏览器空间紧张时可能回收缓存)');
el('offline-enable').disabled = busy || !online;
el('offline-disable').disabled = busy || online;
}
function onProgress(info) {
if (info.done) return;
el('offline-progress').value = Math.min(100, (info.bytes / EXPECTED_BYTES) * 100);
el('offline-progress-text').textContent =
'已解包 ' + info.files + ' 个文件(' + fmtMB(info.bytes) + ')…';
}
function withModule(fn) {
return import(MODULE_URL).then(fn);
}
function refresh() {
if (!('serviceWorker' in navigator) || !('caches' in window)) {
el('offline-state').textContent = '当前浏览器不支持离线模式(需要 Service Worker)';
el('offline-enable').disabled = true;
el('offline-disable').disabled = true;
return;
}
withModule(function (mod) { return mod.ensureFresh(); }).then(function (fresh) {
render(fresh.ready ? fresh.meta : null);
if (fresh.stale) {
setMessage('检测到站点压缩包已更新,离线缓存已自动失效,当前处于在线模式。可重新启用离线模式。');
}
}).catch(function (e) {
setMessage('状态检测失败:' + (e && e.message ? e.message : e));
});
}
function onEnable() {
busy = true;
render(null);
showProgress(true);
setMessage('正在下载并解包站点压缩包,请保持网络连接…');
withModule(function (mod) { return mod.activateOffline({ onProgress: onProgress }); })
.then(function (meta) {
el('offline-progress').value = 100;
el('offline-progress-text').textContent = '解包完成';
setMessage('离线模式已启用。之后访问本博客将优先使用本地缓存,断网也能阅读。');
busy = false;
showProgress(false);
render(meta); // 立即展示离线状态,不必等后台刷新
})
.catch(function (e) {
setMessage('启用失败:' + (e && e.message ? e.message : e));
busy = false;
showProgress(false);
refresh();
});
}
function onDisable() {
busy = true;
setMessage('');
withModule(function (mod) { return mod.deactivateOffline(); })
.then(function () {
setMessage('已切回在线模式,之后页面将通过网络加载。');
busy = false;
render(null);
if (navigator.onLine) {
setTimeout(function () { location.reload(); }, 800);
}
})
.catch(function (e) {
setMessage('操作失败:' + (e && e.message ? e.message : e));
busy = false;
refresh();
});
}
function bind() {
var enable = el('offline-enable');
if (!enable) return; // 不在控制页(PJAX 容错)
enable.addEventListener('click', onEnable);
el('offline-disable').addEventListener('click', onDisable);
refresh();
}
bind();
})();
+269
View File
@@ -0,0 +1,269 @@
/*
* Mayx的博客 · 离线模式核心逻辑(/offline.html 控制页使用,ES module)
*
* - activateOffline():注册 SW,下载站点压缩包 /MayxBlog.7z,经 libarchive(WASM)
* 流式解包,把每个文件按站点路径写入 Cache Storage,最后写入就绪标记
* (清单 + payloadSize 版本号),自此进入离线模式。
* - ensureFresh():HEAD 探测线上压缩包大小,与标记中的 payloadSize 不一致
* 说明站点已更新 → 清空缓存,回退在线模式。
* - deactivateOffline():清空缓存,手动切回在线模式。
* - readStatus():读取当前离线状态,供控制页展示。
*
*/
const ARCHIVE_URL = '/MayxBlog.7z';
const ARCHIVE_ROOT = 'blog/';
const CACHE_NAME = 'mayxblog-offline-v1';
const READY_KEY = '/__offline_manifest__';
const WORKER_URL = '/vendor/libarchive/worker-bundle.js';
const MIME = {
html: 'text/html; charset=utf-8',
htm: 'text/html; charset=utf-8',
css: 'text/css; charset=utf-8',
js: 'application/javascript; charset=utf-8',
mjs: 'application/javascript; charset=utf-8',
json: 'application/json; charset=utf-8',
xml: 'application/xml; charset=utf-8',
xsl: 'text/xsl; charset=utf-8',
opml: 'text/x-opml; charset=utf-8',
txt: 'text/plain; charset=utf-8',
md: 'text/markdown; charset=utf-8',
csv: 'text/csv; charset=utf-8',
svg: 'image/svg+xml',
png: 'image/png',
jpg: 'image/jpeg',
jpeg: 'image/jpeg',
gif: 'image/gif',
webp: 'image/webp',
avif: 'image/avif',
ico: 'image/x-icon',
bmp: 'image/bmp',
woff: 'font/woff',
woff2: 'font/woff2',
ttf: 'font/ttf',
otf: 'font/otf',
eot: 'application/vnd.ms-fontobject',
mp3: 'audio/mpeg',
mp4: 'video/mp4',
webm: 'video/webm',
'7z': 'application/x-7z-compressed',
zip: 'application/zip',
wasm: 'application/wasm',
mtn: 'application/octet-stream',
moc: 'application/octet-stream',
pdf: 'application/pdf',
};
function mimeFor(pathname) {
const dot = pathname.lastIndexOf('.');
const ext = dot === -1 ? '' : pathname.slice(dot + 1).toLowerCase();
return MIME[ext] || 'application/octet-stream';
}
function sitePathFor(entryPath) {
const normalized = entryPath.replace(/\\/g, '/');
if (!normalized.startsWith(ARCHIVE_ROOT)) return null;
const relative = normalized.slice(ARCHIVE_ROOT.length);
if (!relative) return null;
const segments = [];
for (const segment of relative.split('/')) {
if (!segment || segment === '.') continue;
if (segment === '..') return null;
segments.push(segment);
}
if (!segments.length) return null;
return '/' + segments.join('/');
}
/*
* 在独立 worker 中打开一层压缩包并流式吐出条目;
* 返回其中根级嵌套压缩包(下一层包装)的原始字节。
*/
function extractLayer(file, onEntry) {
return new Promise((resolve, reject) => {
const worker = new Worker(WORKER_URL);
const nested = [];
let settled = false;
const finish = (error) => {
if (settled) return;
settled = true;
worker.terminate();
if (error) reject(error);
else resolve(nested);
};
worker.addEventListener('error', (event) => {
finish(new Error(event.message || 'libarchive worker failed'));
});
worker.addEventListener('message', ({ data }) => {
if (data.type === 'ERROR') {
finish(new Error(data.error && data.error.message ? data.error.message : 'libarchive failed'));
return;
}
if (data.type === 'READY') {
worker.postMessage({ type: 'OPEN', file });
return;
}
if (data.type === 'OPENED') {
worker.postMessage({ type: 'EXTRACT_FILES' });
return;
}
if (data.type === 'END') {
finish(null);
return;
}
if (data.type !== 'ENTRY') return;
const entry = data.entry;
if (entry.type !== 'FILE' || !entry.fileData) return;
// 根级压缩包是下一层包装,不是站点内容
if (entry.path.replace(/\\/g, '/') === 'MayxBlog.7z') {
nested.push(entry.fileData);
return;
}
onEntry(entry);
});
});
}
/* 几十 MB 的缓存是浏览器常见的回收对象,而重新解包代价高,尽量申请持久存储。 */
async function requestPersistence() {
if (!navigator.storage || !navigator.storage.persist) return false;
try {
if (await navigator.storage.persisted()) return true;
return await navigator.storage.persist();
} catch {
return false;
}
}
/* 读取就绪标记(= 离线模式状态);损坏或缺清单的标记视为不存在。 */
export async function readStatus() {
const cache = await caches.open(CACHE_NAME);
const hit = await cache.match(READY_KEY);
if (!hit) return null;
const meta = await hit.json().catch(() => null);
if (!meta || !Array.isArray(meta.keys) || !meta.keys.length) return null;
return meta;
}
/*
* 线上压缩包的大小即版本号。HEAD 不被 SW 拦截,拿到的一定是线上真实值;
* 网络失败返回 null,调用方选择信任现有缓存而不是误伤。
*/
async function payloadSize() {
try {
const response = await fetch(ARCHIVE_URL, { method: 'HEAD', cache: 'no-store' });
if (!response.ok) return null;
const size = Number(response.headers.get('content-length'));
return Number.isFinite(size) && size > 0 ? size : null;
} catch {
return null;
}
}
/*
* 控制页打开时调用:未启用 → {ready:false};已启用且版本一致 → {ready:true, meta};
* 版本不一致 → 清空缓存并返回 {ready:false, stale:true}(已回退在线模式)。
*/
export async function ensureFresh() {
const cache = await caches.open(CACHE_NAME);
const size = await payloadSize();
const meta = await readStatus();
if (!meta) return { ready: false, size, stale: false };
if (size && meta.payloadSize !== size) {
await caches.delete(CACHE_NAME);
return { ready: false, size, stale: true };
}
return { ready: true, size, stale: false, meta };
}
/*
* 启用离线模式:注册 SW → 清空旧缓存(同时清掉上次中断的残留,保证随后
* 的压缩包下载不会被旧缓存喂旧的 quine 副本)→ 下载并逐层解包 → 写标记。
*/
export async function activateOffline({ onProgress } = {}) {
if (!('serviceWorker' in navigator)) {
throw new Error('当前浏览器不支持 Service Worker,无法启用离线模式。');
}
await navigator.serviceWorker.register('/sw.js', { scope: '/' });
await navigator.serviceWorker.ready;
await caches.delete(CACHE_NAME);
const persisted = await requestPersistence();
// 写操作串行链,保证按到达顺序落盘且不产生无界并发
const cache = await caches.open(CACHE_NAME);
let writes = Promise.resolve();
const keys = new Set();
let files = 0;
let bytes = 0;
let skipped = 0;
const response = await fetch(ARCHIVE_URL, { cache: 'no-store' });
if (!response.ok) throw new Error(`无法下载站点压缩包:HTTP ${response.status}`);
const outer = await response.arrayBuffer();
const downloadedBytes = outer.byteLength;
let layer = [outer];
while (layer.length) {
const bytesThisLayer = layer.shift();
const file = new File([bytesThisLayer], 'MayxBlog.7z');
layer = await extractLayer(file, (entry) => {
const key = sitePathFor(entry.path);
if (!key) {
skipped += 1;
return;
}
const body = entry.fileData;
files += 1;
bytes += body.byteLength;
writes = writes.then(async () => {
await cache.put(key, new Response(body, {
status: 200,
headers: { 'Content-Type': mimeFor(key) },
}));
keys.add(key);
});
if (onProgress) onProgress({ done: false, files, bytes, skipped });
});
}
await writes;
const meta = {
files,
bytes,
skipped,
persisted,
payloadSize: downloadedBytes,
failed: files - keys.size,
keys: [...keys],
unpackedAt: new Date().toISOString(),
};
await cache.put(
READY_KEY,
new Response(JSON.stringify(meta), {
headers: { 'Content-Type': 'application/json; charset=utf-8' },
})
);
if (onProgress) onProgress({ done: true, cached: false, ...meta });
return meta;
}
/* 切回在线模式:删掉缓存(含标记),SW 随即恢复纯转发。返回之前是否处于离线模式。 */
export async function deactivateOffline() {
const had = await readStatus();
await caches.delete(CACHE_NAME);
return Boolean(had);
}
+1 -1
View File
@@ -116,7 +116,7 @@
// 排除列表:外链、锚点、静态资源、Live2D 目录
var exclude = ':not([target="_blank"]):not([href^="http"]):not([href^="//"])' +
':not([href^="mailto"]):not([href^="#"])' +
':not([href$=".xml"]):not([href$=".json"]):not([href$=".tgz"]):not([href$=".zip"])' +
':not([href$=".xml"]):not([href$=".json"]):not([href$=".7z"]):not([href$=".zip"])' +
':not([href^="/Live2dHistoire"])';
$(document).pjax('a' + exclude, PJAX_OPTS.container, PJAX_OPTS);
$(document).on('submit', 'form#search-input-all', function (e) {
+5 -2
View File
@@ -2,5 +2,8 @@
mkdir Mabbs
curl -L -o Mabbs/README.md https://github.com/Mabbs/Mabbs/raw/main/README.md
bundle exec jekyll build -d public
tar czvf MayxBlog.tgz public/
mv MayxBlog.tgz public/
python3 _tools/blogquine.py --src-dir blog --quine-dir blog public MayxBlog.7z
curl -L -O https://github.com/ip7z/7zip/releases/download/26.03/7z2603-linux-x64.tar.xz
tar xvf 7z2603-linux-x64.tar.xz
./7zzs a -mx9 -bd -bb0 MayxBlog-new.7z MayxBlog.7z
mv MayxBlog-new.7z public/MayxBlog.7z
+10
View File
@@ -0,0 +1,10 @@
---
layout: default
title: Mayx的游戏列表
---
# Mayx的游戏列表
- 凯露&危险生存( [CHS]({% unless site.github %}https://mabbs.github.io{% endunless %}/karyl-yabaival/?cn) \| [JA]({% unless site.github %}https://mabbs.github.io{% endunless %}/karyl-yabaival/) \| [KO]({% unless site.github %}https://mabbs.github.io{% endunless %}/karyl-yabaival/?kr) )
- [立直麻将 Wordle]({% unless site.github %}https://mabbs.github.io{% endunless %}/riichi-mahjong-wordle/)
- [Brainfuck可视化演示]({% unless site.github %}https://mabbs.github.io{% endunless %}/brainfuck-visualizer/)
- [《激情麻将》关服前快照]({% unless site.github %}https://mabbs.github.io{% endunless %}/mahjong-dream-snapshot/) ([国际服]({% unless site.github %}https://mabbs.github.io{% endunless %}/mahjong-dream-snapshot/?intl))
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 748 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

+2 -4
View File
@@ -66,10 +66,8 @@ image: https://screenshot.mayx.eu.org/
<h2>其他页面</h2>
<p>
<a href="/service.html">Mayx的公开服务</a><br />
<a href="{% unless site.github %}https://mabbs.github.io{% endunless %}/linux/">Mayx's Blog Terminal</a><br />
凯露&危险生存( <a href="{% unless site.github %}https://mabbs.github.io{% endunless %}/karyl-yabaival/?cn">CHS</a> | <a href="{% unless site.github %}https://mabbs.github.io{% endunless %}/karyl-yabaival/">JA</a> | <a href="{% unless site.github %}https://mabbs.github.io{% endunless %}/karyl-yabaival/?kr">KO</a> )<br />
<a href="{% unless site.github %}https://mabbs.github.io{% endunless %}/riichi-mahjong-wordle/">立直麻将 Wordle</a><br />
<a href="{% unless site.github %}https://mabbs.github.io{% endunless %}/brainfuck-visualizer/">Brainfuck可视化演示</a><br />
<a href="{% unless site.github %}https://mabbs.github.io{% endunless %}/linux/">Mayx's Blog Terminal</a> (<a href="{% unless site.github %}https://mabbs.github.io{% endunless %}/jsnix/">Fake Version</a>)<br />
<a href="/games.html">Mayx的游戏列表</a><br />
<a href="/message.html">留言板</a><br />
<a href="/links.html">Links</a><br />
<a href="/proxylist.html">代理列表</a><br />
+37
View File
@@ -0,0 +1,37 @@
---
layout: default
title: 离线模式
---
# 离线模式
本页用于管理本博客的离线阅读功能:
- **启用离线模式**:浏览器会下载整站压缩包(`MayxBlog.7z`)并解包到本地缓存,之后即使没有网络也能正常浏览博客;
- **自动回退**:当站点压缩包更新(博客重新部署)时,离线缓存会自动失效并回退到在线模式,下次重新启用即可获取最新内容;
- **手动切换**:离线模式下可以随时在本页切回在线模式,切回后页面将重新通过网络加载。
<table>
<tbody>
<tr><th>当前状态</th><td id="offline-state">检测中…</td></tr>
<tr><th>缓存版本</th><td id="offline-version">-</td></tr>
<tr><th>文件统计</th><td id="offline-files">-</td></tr>
<tr><th>解包时间</th><td id="offline-time">-</td></tr>
<tr><th>持久存储</th><td id="offline-persist">-</td></tr>
</tbody>
</table>
<p id="offline-progress-box" hidden="hidden">
<progress id="offline-progress" max="100" value="0"></progress>
<small id="offline-progress-text">准备中…</small>
</p>
<p><strong id="offline-message"></strong></p>
<p>
<button type="button" id="offline-enable">启用离线模式</button>
<button type="button" id="offline-disable">切换回在线模式</button>
</p>
<script src="/assets/js/offline-page.js"></script>
+140
View File
@@ -0,0 +1,140 @@
/*
* Mayx的博客 · 离线模式 Service Worker
*
* 两种模式(由 Cache Storage 中是否存在就绪标记决定):
* - 在线模式(默认,无标记):所有请求原样转发网络,SW 形同虚设。
* - 离线模式(/offline.html 启用后写入标记):同源 GET 请求缓存优先,
* 缓存未命中且清单里没有的路径才回源网络,实现断网阅读。
*
* 版本控制:压缩包 /MayxBlog.7z 的 content-length 即版本号(每次重新部署
* 都会变化)。SW 激活时、以及导航请求按 RECHECK_INTERVAL 节流复核;
* 一旦发现线上压缩包与已缓存版本不一致,立即清空缓存 → 自动回退在线模式。
* 网络不可用时复核失败,保留缓存(离线阅读本来就是离线模式的目的)。
*/
const CACHE_NAME = 'mayxblog-offline-v1';
const READY_KEY = '/__offline_manifest__';
const ARCHIVE_URL = '/MayxBlog.7z';
const RECHECK_INTERVAL = 5 * 60 * 1000;
let lastRecheck = 0;
self.addEventListener('install', () => {
self.skipWaiting();
});
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys()
.then((keys) =>
Promise.all(keys.filter((k) => k !== CACHE_NAME).map((k) => caches.delete(k)))
)
.then(verifyPayloadVersion)
.then(() => self.clients.claim())
);
});
/*
* HEAD 请求不被下面的 fetch 处理器拦截,这里读到的一定是线上真实压缩包,
* 而不是缓存里的 quine 副本。网络失败返回 null,调用方选择信任现有缓存。
*/
async function payloadSize() {
try {
const response = await fetch(ARCHIVE_URL, { method: 'HEAD', cache: 'no-store' });
if (!response.ok) return null;
const size = Number(response.headers.get('content-length'));
return Number.isFinite(size) && size > 0 ? size : null;
} catch {
return null;
}
}
/*
* 就绪标记同时是清单:记录了解包写入的所有路径。没有它就无法区分
* “站点本来就没有这个页面”和“缓存条目被浏览器回收了”。
*/
async function readManifest(cache) {
const hit = await cache.match(READY_KEY);
if (!hit) return null;
const meta = await hit.json().catch(() => null);
if (!meta || !Array.isArray(meta.keys) || !meta.keys.length) return null;
return meta;
}
/* 压缩包更新 → 清空缓存(即回退在线模式);返回是否发生了失效。 */
async function verifyPayloadVersion() {
const cache = await caches.open(CACHE_NAME);
const meta = await readManifest(cache);
if (!meta) return false;
const size = await payloadSize();
if (size && meta.payloadSize !== size) {
await caches.delete(CACHE_NAME);
return true;
}
return false;
}
/* 服务器对目录索引的处理与 Jekyll 产物一致,这里补齐候选路径。 */
function candidatesFor(pathname) {
const keys = [pathname];
if (pathname.endsWith('/')) {
keys.push(pathname + 'index.html');
} else {
keys.push(pathname + '/index.html');
keys.push(pathname + '.html');
}
return keys;
}
self.addEventListener('fetch', (event) => {
const request = event.request;
if (request.method !== 'GET') return; // HEAD 等直通网络(版本探测依赖这一点)
const url = new URL(request.url);
if (url.origin !== self.location.origin) return; // 跨域请求不干预
// 显式 no-store 的请求(重新下载压缩包等)绕过缓存,避免吃到旧的 quine 副本
if (request.cache === 'no-store') return;
event.respondWith(respond(request, url, event));
});
async function respond(request, url, event) {
const cache = await caches.open(CACHE_NAME);
// 导航请求顺带做节流版本复核;失效发生在后台,本次响应不受影响,
// 下一次请求自然落入在线模式。
if (request.mode === 'navigate' && Date.now() - lastRecheck > RECHECK_INTERVAL) {
lastRecheck = Date.now();
event.waitUntil(verifyPayloadVersion());
}
const candidates = candidatesFor(url.pathname);
for (const key of candidates) {
const hit = await cache.match(key);
if (hit) return hit;
}
const meta = await readManifest(cache);
if (!meta) return fetch(request); // 在线模式:纯转发
// 清单承诺过这个路径但缓存里没有 → 条目被浏览器回收,缓存已不可信,
// 整体作废并回退在线模式。
if (candidates.some((key) => meta.keys.includes(key))) {
await caches.delete(CACHE_NAME);
return fetch(request);
}
// 清单之外的路径回源网络;断网时的导航请求用站点 404 页兜底。
try {
return await fetch(request);
} catch (error) {
if (request.mode === 'navigate') {
const notFound = await cache.match('/404.html');
if (notFound) {
return new Response(notFound.body, { status: 404, headers: notFound.headers });
}
}
throw error;
}
}
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 ნიკა
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+15
View File
@@ -0,0 +1,15 @@
var libarchive = (function() {
var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined;
return (
function(libarchive) {
libarchive = libarchive || {};
var Module=typeof libarchive!=="undefined"?libarchive:{};var moduleOverrides={};var key;for(key in Module){if(Module.hasOwnProperty(key)){moduleOverrides[key]=Module[key]}}Module["arguments"]=[];Module["thisProgram"]="./this.program";Module["quit"]=function(status,toThrow){throw toThrow};Module["preRun"]=[];Module["postRun"]=[];var ENVIRONMENT_IS_WEB=false;var ENVIRONMENT_IS_WORKER=false;var ENVIRONMENT_IS_NODE=false;var ENVIRONMENT_IS_SHELL=false;ENVIRONMENT_IS_WEB=typeof window==="object";ENVIRONMENT_IS_WORKER=typeof importScripts==="function";ENVIRONMENT_IS_NODE=typeof process==="object"&&typeof require==="function"&&!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_WORKER;ENVIRONMENT_IS_SHELL=!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_NODE&&!ENVIRONMENT_IS_WORKER;var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}else{return scriptDirectory+path}}if(ENVIRONMENT_IS_NODE){scriptDirectory=__dirname+"/";var nodeFS;var nodePath;Module["read"]=function shell_read(filename,binary){var ret;if(!nodeFS)nodeFS=require("fs");if(!nodePath)nodePath=require("path");filename=nodePath["normalize"](filename);ret=nodeFS["readFileSync"](filename);return binary?ret:ret.toString()};Module["readBinary"]=function readBinary(filename){var ret=Module["read"](filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}assert(ret.buffer);return ret};if(process["argv"].length>1){Module["thisProgram"]=process["argv"][1].replace(/\\/g,"/")}Module["arguments"]=process["argv"].slice(2);process["on"]("uncaughtException",function(ex){if(!(ex instanceof ExitStatus)){throw ex}});process["on"]("unhandledRejection",abort);Module["quit"]=function(status){process["exit"](status)};Module["inspect"]=function(){return"[Emscripten Module object]"}}else if(ENVIRONMENT_IS_SHELL){if(typeof read!="undefined"){Module["read"]=function shell_read(f){return read(f)}}Module["readBinary"]=function readBinary(f){var data;if(typeof readbuffer==="function"){return new Uint8Array(readbuffer(f))}data=read(f,"binary");assert(typeof data==="object");return data};if(typeof scriptArgs!="undefined"){Module["arguments"]=scriptArgs}else if(typeof arguments!="undefined"){Module["arguments"]=arguments}if(typeof quit==="function"){Module["quit"]=function(status){quit(status)}}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptDir){scriptDirectory=_scriptDir}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.lastIndexOf("/")+1)}else{scriptDirectory=""}Module["read"]=function shell_read(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){Module["readBinary"]=function readBinary(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}Module["readAsync"]=function readAsync(url,onload,onerror){var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=function xhr_onload(){if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)};Module["setWindowTitle"]=function(title){document.title=title}}else{}var out=Module["print"]||(typeof console!=="undefined"?console.log.bind(console):typeof print!=="undefined"?print:null);var err=Module["printErr"]||(typeof printErr!=="undefined"?printErr:typeof console!=="undefined"&&console.warn.bind(console)||out);for(key in moduleOverrides){if(moduleOverrides.hasOwnProperty(key)){Module[key]=moduleOverrides[key]}}moduleOverrides=undefined;function dynamicAlloc(size){var ret=HEAP32[DYNAMICTOP_PTR>>2];var end=ret+size+15&-16;if(end<=_emscripten_get_heap_size()){HEAP32[DYNAMICTOP_PTR>>2]=end}else{var success=_emscripten_resize_heap(end);if(!success)return 0}return ret}function getNativeTypeSize(type){switch(type){case"i1":case"i8":return 1;case"i16":return 2;case"i32":return 4;case"i64":return 8;case"float":return 4;case"double":return 8;default:{if(type[type.length-1]==="*"){return 4}else if(type[0]==="i"){var bits=parseInt(type.substr(1));assert(bits%8===0,"getNativeTypeSize invalid bits "+bits+", type "+type);return bits/8}else{return 0}}}}var asm2wasmImports={"f64-rem":function(x,y){return x%y},"debugger":function(){debugger}};var functionPointers=new Array(0);var tempRet0=0;var setTempRet0=function(value){tempRet0=value};if(typeof WebAssembly!=="object"){err("no native wasm support detected")}var wasmMemory;var wasmTable;var ABORT=false;var EXITSTATUS=0;function assert(condition,text){if(!condition){abort("Assertion failed: "+text)}}function getCFunc(ident){var func=Module["_"+ident];assert(func,"Cannot call unknown function "+ident+", make sure it is exported");return func}function ccall(ident,returnType,argTypes,args,opts){var toC={"string":function(Line truncated
return libarchive
}
);
})();
export default libarchive;
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
!function(){"use strict";const e={32768:"FILE",16384:"DIR",40960:"SYMBOLIC_LINK",49152:"SOCKET",8192:"CHARACTER_DEVICE",24576:"BLOCK_DEVICE",4096:"NAMED_PIPE"};class r{constructor(e){this._wasmModule=e,this._runCode=e.runCode,this._file=null,this._passphrase=null}open(e){null!==this._file&&(console.warn("Closing previous file"),this.close());const{promise:r,resolve:t,reject:n}=this._promiseHandles();this._file=e;const o=new FileReader;return o.onload=(()=>this._loadFile(o.result,t,n)),o.readAsArrayBuffer(e),r}close(){this._runCode.closeArchive(this._archive),this._wasmModule._free(this._filePtr),this._file=null,this._filePtr=null,this._archive=null}hasEncryptedData(){this._archive=this._runCode.openArchive(this._filePtr,this._fileLength,this._passphrase),this._runCode.getNextEntry(this._archive);const e=this._runCode.hasEncryptedEntries(this._archive);return 0!==e&&(e>0||null)}setPassphrase(e){this._passphrase=e}*entries(r=!1,t=null){let n;for(this._archive=this._runCode.openArchive(this._filePtr,this._fileLength,this._passphrase);0!==(n=this._runCode.getNextEntry(this._archive));){const o={size:this._runCode.getEntrySize(n),path:this._runCode.getEntryName(n),type:e[this._runCode.getEntryType(n)],ref:n};if("FILE"===o.type){let e=o.path.split("/");o.fileName=e[e.length-1]}if(r&&t!==o.path)this._runCode.skipEntry(this._archive);else{const e=this._runCode.getFileData(this._archive,o.size);if(e<0)throw new Error(this._runCode.getError(this._archive));o.fileData=this._wasmModule.HEAP8.slice(e,e+o.size),this._wasmModule._free(e)}yield o}}_loadFile(e,r,t){try{const n=new Uint8Array(e);this._fileLength=n.length,this._filePtr=this._runCode.malloc(this._fileLength),this._wasmModule.HEAP8.set(n,this._filePtr),r()}catch(e){t(e)}}_promiseHandles(){let e=null,r=null;return{promise:new Promise((t,n)=>{e=t,r=n}),resolve:e,reject:r}}}var t,n=(t="undefined"!=typeof document&&document.currentScript?document.currentScript.src:void 0,function(e){var r,n=void 0!==(e=e||{})?e:{},o={};for(r in n)n.hasOwnProperty(r)&&(o[r]=n[r]);n.arguments=[],n.thisProgram="./this.program",n.quit=function(e,r){throw r},n.preRun=[],n.postRun=[];var i,a,s=!1,u=!1;s="object"==typeof window,u="function"==typeof importScripts,i="object"==typeof process&&"function"==typeof require&&!s&&!u,a=!s&&!i&&!u;var c,f,l="";i?(l=__dirname+"/",n.read=function(e,r){var t;return c||(c=require("fs")),f||(f=require("path")),e=f.normalize(e),t=c.readFileSync(e),r?t:t.toString()},n.readBinary=function(e){var r=n.read(e,!0);return r.buffer||(r=new Uint8Array(r)),E(r.buffer),r},process.argv.length>1&&(n.thisProgram=process.argv[1].replace(/\\/g,"/")),n.arguments=process.argv.slice(2),process.on("uncaughtException",function(e){if(!(e instanceof qe))throw e}),process.on("unhandledRejection",Ze),n.quit=function(e){process.exit(e)},n.inspect=function(){return"[Emscripten Module object]"}):a?("undefined"!=typeof read&&(n.read=function(e){return read(e)}),n.readBinary=function(e){var r;return"function"==typeof readbuffer?new Uint8Array(readbuffer(e)):(E("object"==typeof(r=read(e,"binary"))),r)},"undefined"!=typeof scriptArgs?n.arguments=scriptArgs:void 0!==arguments&&(n.arguments=arguments),"function"==typeof quit&&(n.quit=function(e){quit(e)})):(s||u)&&(u?l=self.location.href:document.currentScript&&(l=document.currentScript.src),t&&(l=t),l=0!==l.indexOf("blob:")?l.substr(0,l.lastIndexOf("/")+1):"",n.read=function(e){var r=new XMLHttpRequest;return r.open("GET",e,!1),r.send(null),r.responseText},u&&(n.readBinary=function(e){var r=new XMLHttpRequest;return r.open("GET",e,!1),r.responseType="arraybuffer",r.send(null),new Uint8Array(r.response)}),n.readAsync=function(e,r,t){var n=new XMLHttpRequest;n.open("GET",e,!0),n.responseType="arraybuffer",n.onload=function(){200==n.status||0==n.status&&n.response?r(n.response):t()},n.onerror=t,n.send(null)},n.setWindowTitle=function(e){document.title=e});var d=n.print||("undefined"!=typeof console?console.log.bind(console):"undefined"!=typeof print?print:null),p=n.printErr||("undefined"!=typeof printErr?printErr:"undefined"!=typeof console&&console.warn.bind(console)||d);for(r in o)o.hasOwnProperty(r)&&(n[r]=o[r]);function m(e){var r=M[q>>2],t=r+e+15&-16;if(t<=Ae())M[q>>2]=t;else if(!Re(t))return 0;return r}function h(e){switch(e){case"i1":case"i8":return 1;case"i16":return 2;case"i32":return 4;case"i64":return 8;case"float":return 4;case"double":return 8;default:if("*"===e[e.length-1])return 4;if("i"===e[0]){var r=parseInt(e.substr(1));return E(r%8==0,"getNativeTypeSize invalid bits "+r+", type "+e),r/8}return 0}}o=void 0;var v,w={"f64-rem":function(e,r){return e%r},debugger:function(){}};"object"!=typeof WebAssembly&&p("no native wasm support detected");var y=!1;function E(e,r){e||Ze("Assertion failed: "+r)}function g(e){var r=n["_"+e];return E(r,"Cannot call unknown function "+e+", make sure it is exported"),r}function _(e,r,t,n,o){var i={string:function(e){var r=0;if(null!=e&&0!==e){var t=1+(e.length<<2);C(e,r=je(t),t)}return r},arrayLine truncated