5 Commits

Author SHA1 Message Date
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
12 changed files with 2097 additions and 58 deletions

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

View File

@@ -181,5 +181,6 @@
"/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/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/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不仅可以重组现有信息,还可以完成人类以前认为具有挑战性的任务。"
}

View File

@@ -26,3 +26,4 @@ RavelloH's Blog,https://ravelloh.com,https://ravelloh.com/feed.xml,Beginning of
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
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 把喜欢的东西都记下来···

View File

@@ -4,6 +4,7 @@ http://git.dkforestseeaaq2dqz2uflmlsybvnq2irzn4ygyvu53oazyorednviid.onion/mayx/b
http://giteabolfdejtdzblkooalqei6jr67imiugmhtsh6ocw4hlj5a4q.b32.i2p/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
@@ -31,20 +32,16 @@ http://116.236.50.103:8789/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
http://187.216.152.151:9999/mayx/blog
http://116.63.173.179:8001/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://gitea.sciotech.cn/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://120.24.249.56/mayx/blog
http://120.211.66.170:8418/mayx/blog
http://209.127.59.74:3000/mayx/blog
http://121.41.2.71:3000/mayx/blog
@@ -65,11 +62,9 @@ http://8.130.135.159:3000/mayx/blog
https://git.dshkabatur.ru/mayx/blog
http://47.103.78.70: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
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
http://39.101.74.135:5000/mayx/blog_cn
@@ -81,20 +76,16 @@ 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://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://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://gitea.jnyuxia.com/mayx/blog
https://gitea.mynas-lechner.de/mayx/blog
https://gitlab.iplusus.com/mayx/blog
https://git.miasma-os.com/mayx/blog
https://git.zakum.cn/mayx/blog
http://47.109.103.110:9000/mayx/blog_cn
http://47.104.241.192:19999/mayx/blog_cn
http://47.98.148.146:1026/mayx/blog_cn
@@ -107,20 +98,15 @@ http://47.106.222.181:20511/mayx/blog_cn
http://118.24.161.24:3000/mayx/blog_cn
http://36.138.125.206:3000/mayx/blog_cn
https://git.crystalyx.net/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
http://gitea.snailtrack.cn/mayx/blog_cn
https://code.draussenfunker.de/mayx/blog_cn
https://git.dinsor.co.th/mayx/blog
http://www.bkandssp.cn:30/mayx/blog
http://gitea.ydxtool.com/mayx/blog
https://gitea.malxte.de/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
http://210.75.240.13:3000/mayx/blog_cn
http://git.hbg99.com:8080/mayx/blog_cn
https://git.eplg.services/mayx/blog
@@ -132,18 +118,14 @@ https://git.libregaming.org/mayx/blog
https://git.kaki87.net/mayx/blog
https://forgejo.vanten-s.com/mayx/blog
http://124.71.169.183:3000/mayx/blog
https://git.msoucy.me/mayx/blog
http://140.120.108.238:49308/mayx/blog
http://git.chilidoginteractive.com:3000/mayx/blog_cn
http://git.uhfdemo.com/mayx/blog_cn
http://43.143.142.38:7001/mayx/blog_cn
https://git.panda-number.one/mayx/blog
https://datacortex.pl/mayx/blog
https://gitea.keymantek.com:777/mayx/blog
https://git.tirtapakuan.co.id/mayx/blog
http://tian-you.top:7020/mayx/blog
http://34.17.182.140/mayx-gogs/blog
http://gitea.mikarsoft.com/mayx/blog
https://mcgit.place/mayx/blog
https://forgejo.rekallservices.com/mayx/blog
http://git.instal.org.cn/mayx/blog
@@ -154,7 +136,6 @@ https://git.netzbyte.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://lab.dutt.ch/mayx/blog
https://gitea.dabit.synology.me/mayx/blog
https://git.joinplu.me/mayx/blog
@@ -173,16 +154,12 @@ https://gitbucket.aint-no.info/mayx/blog
https://git.wikeo.ovh/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://81.70.30.91:3000/mayx/blog_cn
http://yidaima.cn:6008/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
https://git.newnaturalphilosophy.org/mayx/blog_cn
https://gitea.detr.top/mayx/blog_cn
http://175.178.193.35:3000/mayx/blog_cn
http://gitea.jb1000.com:88/mayx/blog_cn
http://www.22ai.vip/mayx/blog_cn
@@ -192,54 +169,43 @@ http://62.234.20.54:3000/mayx/blog_cn
https://gitea.4gunn.cn:52443/mayx/blog_cn
http://jdunlap.com/mayx/blog
https://git.silica.codes/mayx/blog
https://www.vitelgrid.app/mayx/blog
https://gitruhub.ru/mayx/blog
https://git.linuxposting.xyz/mayx/blog
https://git.techworkshop42.ru/mayx/blog
http://speedyfox.app/mayx/blog
https://mjjtop.com/mayx/blog
https://forgejo.wanderingmonster.dev/mayx/blog
http://v.udvip.com/mayx/blog
https://git.edavmig.ru/mayx/blog
https://gitea.ns5001k.sigma2.no/mayx/blog
https://fzsimo.com/mayx/blog
https://git.hemangvyas.com/mayx/blog
https://gitav.ru/mayx/blog
https://git.sakuzyo.net/mayx/blog
http://www.365rise.top/mayx/blog
https://git.ifuntanhub.dev/mayx/blog
https://git.aptcloud.ru/mayx/blog
https://gitlab.dev.genai-team.ru/mayx/blog
https://d.roxyipt.com/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
https://gitea.onfing.ir/mayx/blog
https://gitea.augeo.dev/mayx/blog_cn
http://36.213.200.127:23000/mayx/blog_cn
http://20.219.0.85:3000/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://gitimn.com/mayx/blog
http://git.zapthisblog.com/mayx/blog
https://git.resacachile.cl/mayx/blog
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
http://gitea.amm.ar/mayx/blog
https://git.83channel.net/mayx/blog
https://git.zeppone.com/mayx/blog
https://git.telecom.quest/mayx/blog
https://repo.paperless.rs/mayx/blog
https://urbal.dev/mayx/blog
https://git.pwarde.nl/mayx/blog
https://presko.info/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
4 http://giteabolfdejtdzblkooalqei6jr67imiugmhtsh6ocw4hlj5a4q.b32.i2p/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
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
36 http://187.216.152.151:9999/mayx/blog
37 http://116.63.173.179:8001/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://gitea.sciotech.cn/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://120.24.249.56/mayx/blog
45 http://120.211.66.170:8418/mayx/blog
46 http://209.127.59.74:3000/mayx/blog
47 http://121.41.2.71:3000/mayx/blog
62 https://git.dshkabatur.ru/mayx/blog
63 http://47.103.78.70: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
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
70 http://39.101.74.135:5000/mayx/blog_cn
76 http://118.24.129.148:3000/mayx/blog_cn
77 http://120.48.141.82:3000/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
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
86 https://gitea.jnyuxia.com/mayx/blog
https://gitea.mynas-lechner.de/mayx/blog
87 https://gitlab.iplusus.com/mayx/blog
88 https://git.miasma-os.com/mayx/blog
https://git.zakum.cn/mayx/blog
89 http://47.109.103.110:9000/mayx/blog_cn
90 http://47.104.241.192:19999/mayx/blog_cn
91 http://47.98.148.146:1026/mayx/blog_cn
98 http://118.24.161.24:3000/mayx/blog_cn
99 http://36.138.125.206:3000/mayx/blog_cn
100 https://git.crystalyx.net/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
102 http://gitea.snailtrack.cn/mayx/blog_cn
103 https://code.draussenfunker.de/mayx/blog_cn
https://git.dinsor.co.th/mayx/blog
104 http://www.bkandssp.cn:30/mayx/blog
105 http://gitea.ydxtool.com/mayx/blog
106 https://gitea.malxte.de/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
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
118 https://git.kaki87.net/mayx/blog
119 https://forgejo.vanten-s.com/mayx/blog
120 http://124.71.169.183:3000/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
http://git.uhfdemo.com/mayx/blog_cn
http://43.143.142.38:7001/mayx/blog_cn
123 https://git.panda-number.one/mayx/blog
124 https://datacortex.pl/mayx/blog
125 https://gitea.keymantek.com:777/mayx/blog
126 https://git.tirtapakuan.co.id/mayx/blog
127 http://tian-you.top:7020/mayx/blog
128 http://34.17.182.140/mayx-gogs/blog
http://gitea.mikarsoft.com/mayx/blog
129 https://mcgit.place/mayx/blog
130 https://forgejo.rekallservices.com/mayx/blog
131 http://git.instal.org.cn/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
139 https://lab.dutt.ch/mayx/blog
140 https://gitea.dabit.synology.me/mayx/blog
141 https://git.joinplu.me/mayx/blog
154 https://git.wikeo.ovh/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://81.70.30.91:3000/mayx/blog_cn
159 http://yidaima.cn:6008/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
https://git.newnaturalphilosophy.org/mayx/blog_cn
https://gitea.detr.top/mayx/blog_cn
163 http://175.178.193.35:3000/mayx/blog_cn
164 http://gitea.jb1000.com:88/mayx/blog_cn
165 http://www.22ai.vip/mayx/blog_cn
169 https://gitea.4gunn.cn:52443/mayx/blog_cn
170 http://jdunlap.com/mayx/blog
171 https://git.silica.codes/mayx/blog
https://www.vitelgrid.app/mayx/blog
172 https://gitruhub.ru/mayx/blog
https://git.linuxposting.xyz/mayx/blog
https://git.techworkshop42.ru/mayx/blog
http://speedyfox.app/mayx/blog
173 https://mjjtop.com/mayx/blog
174 https://forgejo.wanderingmonster.dev/mayx/blog
http://v.udvip.com/mayx/blog
https://git.edavmig.ru/mayx/blog
175 https://gitea.ns5001k.sigma2.no/mayx/blog
https://fzsimo.com/mayx/blog
176 https://git.hemangvyas.com/mayx/blog
177 https://gitav.ru/mayx/blog
https://git.sakuzyo.net/mayx/blog
http://www.365rise.top/mayx/blog
https://git.ifuntanhub.dev/mayx/blog
178 https://git.aptcloud.ru/mayx/blog
179 https://gitlab.dev.genai-team.ru/mayx/blog
https://d.roxyipt.com/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
https://gitea.onfing.ir/mayx/blog
https://gitea.augeo.dev/mayx/blog_cn
185 http://36.213.200.127:23000/mayx/blog_cn
http://20.219.0.85:3000/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
https://gitimn.com/mayx/blog
http://git.zapthisblog.com/mayx/blog
https://git.resacachile.cl/mayx/blog
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
http://gitea.amm.ar/mayx/blog
196 https://git.83channel.net/mayx/blog
https://git.zeppone.com/mayx/blog
https://git.telecom.quest/mayx/blog
197 https://repo.paperless.rs/mayx/blog
198 https://urbal.dev/mayx/blog
199 https://git.pwarde.nl/mayx/blog
https://presko.info/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

View File

@@ -19,10 +19,8 @@ mirrors:
- 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.pages.lain.la/
- https://mayx.4everland.app/
- https://mayx.readthedocs.io/
- https://mayx.sourceforge.io/

View File

@@ -98,7 +98,7 @@ 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: 270px; border-right: none;"><a href="/MayxBlog.7z">Download <strong>7-Zip File</strong></a></li>
{% endif %}
</ul>
</header>

1352
_posts/2026-10-01-quine.md Normal file

File diff suppressed because it is too large Load Diff

714
_tools/blogquine.py Normal file
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()

View File

@@ -6,8 +6,8 @@ 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
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
mv MayxBlog.7z ../public_html/
cd ../public_html/
unset GIT_DIR
git init

View File

@@ -6,8 +6,8 @@ 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
mv MayxBlog.7z ../domains/mayx.serv00.net/public_html/
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
@@ -18,6 +18,7 @@ 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

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) {

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