From 7f6baa0c8d5d51a20bfec7867a88f381bf99ab6a Mon Sep 17 00:00:00 2001 From: mayx Date: Wed, 7 Oct 2026 06:16:54 +0000 Subject: Update 2 files - /_tools/serv00_post-receive - /_posts/2026-10-01-quine.md --- _posts/2026-10-01-quine.md | 562 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 562 insertions(+) (limited to '_posts') diff --git a/_posts/2026-10-01-quine.md b/_posts/2026-10-01-quine.md index 237eed8..bef3437 100644 --- a/_posts/2026-10-01-quine.md +++ b/_posts/2026-10-01-quine.md @@ -190,6 +190,568 @@ class BlogQuine: 于是我第一时间就把原来的TGZ压缩命令换掉,换成了AI给我写的[blogquine.py](https://github.com/Mabbs/mabbs.github.io/blob/master/_tools/blogquine.py),现在就可以通过[这里](https://mayx.eu.org/MayxBlog.7z)下载到“完整”包含我博客所有内容的压缩包了。 不过唯一的问题就是这样做出来的压缩包并没有压缩😂,相当于给做成了普通的归档了。当然我的博客本身倒是不大,没压缩也多不了多少空间,但相比于能做出“完整”的效果来说,这点浪费的空间也是小问题了。 +## 基于博客压缩包的离线模式(2026.10.03更新) + 既然现在的AI什么都能做了,那上次我因为技术不足仅仅只是用[Service Worker做了个代理](/2025/08/01/sw-proxy.html),现在也可以升级成下载全站之后让全站离线的功能吧?不过之前做的压缩包并没有压缩,导致整个压缩包有接近20MiB的大小,后来我仔细想了一下我的需求,我只是想让下载全站压缩包的按钮不断链,并没有说解压后的文件一定就必须是它本身,所以我只要用相同的文件名再压缩一次应该就可以了,这样既能满足我的想法,又可以让其他人下载的时候不会花费太多的时间。 + 在我直接压缩之后,整个压缩包占用大概9MiB的空间,其实我已经很满意了,不过压之前是18MiB,也就是说压缩率是50%……虽然也不是不能接受,但我在想这是不是合理的结果呢?我让AI分析了一下50%的压缩率正不正常,它看了一眼之后告诉我文本部分的10MiB压缩完之后有1MiB,图片因为压不动所以直接占用了8MiB😅,这时候我才意识到原来我一直存着一堆占着大量空间的垃圾,而且还是用的PNG/JPG这种压缩率很低的格式……考虑到兼容性和压缩效果,我把占用比较大的图片转换为了WebP格式,最终整个压缩包就只占用了4MiB的空间,一口气又节省了50%的空间。 + 最后做完对压缩包的处理之后,我直接让AI给我基于那个压缩包制作离线模式,它轻轻松松就[做出来](https://mayx.eu.org/offline.html)了,最终使用的是[libarchive库](https://github.com/nika-begiashvili/libarchivejs),在启用离线模式之后整个网站的速度简直就是0延迟,点击就能瞬间跳转,效果算是相当不错了。 +## 受大佬启发制作了能压缩的7z Quine(2026.10.05更新) + 做完博客压缩包之后我突发奇想,虽然东西是AI做的,但也是在我的手中做出了世界上第一个LZMA2 Quine,那么如果我向全世界第一个制作ZIP Quine的人展示一下我的作品会怎么样呢?目前可以知道,世界上第一个制作ZIP Quine的人是[Erling Ellingsen](https://infosec.exchange/@steike),所以我向他发了个帖子[展示了一下](https://infosec.exchange/@mayx/117385801954308033)。大佬不愧是大佬,一眼就能看出理论上能压缩的7z Quine是可以做出来的,对我来说,我是完全不知道7-Zip居然还支持链式编解码,能可以通过“double lzma2”的方式做到既可以自包含、又能真正被压缩的压缩包。既然大佬说理论上可以,那我自然是相信的,于是就让AI给我使劲折腾想办法把它做出来。在折腾了一晚上之后AI终于做出来了,真是强得可怕: +```python +# ============================================================ +# 双层 LZMA2 的 7z quine("double lzma2") +# +# F = S32 ‖ P ‖ H,folder 的 coder 链 = [LZMA2(外层), LZMA2(内层)], +# 解码管线:P --外层--> I --内层--> U。 +# +# I = Cseg ‖ LBseg ‖ Mseg ‖ 0x00 +# Cseg : 真实 LZMA2 对 content 的压缩流(内容真正的压缩数据 C) +# LBseg: 内层 store 链,载荷 = seed(seed 内含一份 C 的参照副本) +# Mseg : 内层 match 链,输出 quine 区(= F 的逐字节副本) +# U = content ‖ seed ‖ F +# +# 外层把 I 再压一遍:Cseg 用 store 链原样承载;seed 里的 C 参照副本与 Cseg +# 逐字节相同,被外层的 rep0 回溯匹配消掉,于是成品里只剩一份压缩数据。 +# quine 区回引 P 的字节时,源全在 seed 里(C 副本 / 头样本表 / 机械串)。 +# ============================================================ +def lzma_chunk(tokens, out_pos): + """编一个 LZMA chunk(0xC0: state+props reset,无 dict reset,无 end marker)。 + match token 的编码字节只取决于 (dist,len,pos),与历史内容无关(不维护输出历史)。 + 解压长度用 21 位(控制字节低 5 位存高 5 位),故单 chunk 输出上限 2 MiB。""" + 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() + u = total - 1 + c = len(data) - 1 + assert 0 <= u < (1 << 21) and 0 <= c < 65536, (total, len(data)) + hdr = bytes([0xC0 | ((u >> 16) & 0x1F)]) + (u & 0xFFFF).to_bytes(2, "big") + \ + (c & 0xFFFF).to_bytes(2, "big") + bytes([PROPS_BYTE]) + return hdr + data, total + +LZ2_MAXU = 1 << 21 # LZMA2 单个 LZMA chunk 的解压上限 + + +def lzma2_raw(data, dict_size, preset=6): + """FORMAT_RAW + LZMA2 压缩;去掉结尾 0x00 便于后面继续追加 chunk。""" + c = lzma.LZMACompressor( + format=lzma.FORMAT_RAW, + filters=[{"id": lzma.FILTER_LZMA2, "dict_size": dict_size, "preset": preset}]) + out = c.compress(data) + c.flush() + assert out and out[-1] == 0x00, "LZMA2 流应以 0x00 结尾" + return out[:-1] + + +def split_len(n, cap): + """按 cap 上限切分 n 字节。末片若为 1 字节则从前一片借 1(match 最短为 2)。""" + Ls, rem = [], n + while rem > cap: + Ls.append(cap) + rem -= cap + if rem == 1 and Ls: + Ls[-1] -= 1 + rem = 2 + Ls.append(rem) + return Ls + + +class DoubleQuine(BlogQuine): + """双层 LZMA2 版本:folder = [LZMA2(外层), LZMA2(内层)]。""" + + def entries(self): + """U 的顺序是 内容文件… ‖ seed ‖ quine,条目顺序必须与之对应。""" + ent = [{"name": r, "dir": isd} for r, isd in self.walk_entries] + ent.append({"name": self.seed_name, "dir": False}) + ent.append({"name": self.quine_name, "dir": False}) + return ent + + # ---------- 头部:双 coder + BindPair ---------- + def build_header(self, n, total, d, entries, sub_sizes, size_I): + 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" + h += b"\x07" # kUnpackInfo + h += b"\x0b" + varint(1) + b"\x00" # kFolder: 1, local + h += varint(2) # NumCoders = 2 + h += b"\x21\x21" + varint(1) + bytes([self.dict_prop_out]) # coder0 = 外层 + h += b"\x21\x21" + varint(1) + bytes([self.dict_prop_in]) # coder1 = 内层 + h += varint(1) + varint(0) # BindPair = (InIndex=1, OutIndex=0) + h += b"\x0c" + varint(size_I) + varint(d + total) # 两个 coder 的输出大小 + h += b"\x00" + h += b"\x08" # SubStreamsInfo + h += b"\x0d" + varint(n_sub) + h += b"\x09" + b"".join(varint(s) for s in sub_sizes[:-1]) + h += b"\x0a" + b"\x01" + crc_base = len(h) + h += b"\x00" * (4 * n_sub) + h += b"\x00\x00" + h += b"\x05" + varint(N) # FilesInfo + bits = bytearray((N + 7) // 8) + for i, e in enumerate(entries): + if e["dir"]: + bits[i // 8] |= 1 << (7 - i % 8) + h += b"\x0e" + varint(len(bits)) + bytes(bits) + mt = b"\x01\x00" + struct.pack("= 2, "段太短 %d @%d" % (ln, fo) + pos += ln + assert pos == total, "段总长 %d != total %d" % (pos, total) + # 生成 match 链,按 2 MiB 上限切成多个 0xC0 chunk + mseg = bytearray() + out_pos = d + cur, cur_len = [], 0 + chunks = [] + for (fo, ln, u) in segs: + dist = (d + fo) - u + assert 0 < dist, "dist=%d @%d" % (dist, fo) + toks = matches_for(dist, ln) + if cur_len + ln > LZ2_MAXU - MAX_MATCH: + chunks.append((cur, out_pos, cur_len)) + out_pos += cur_len + cur, cur_len = [], 0 + cur.extend(toks) + cur_len += ln + if cur: + chunks.append((cur, out_pos, cur_len)) + out_pos += cur_len + assert out_pos == d + total, "Mseg 输出 %d != %d" % (out_pos, d + total) + for (toks, op, cl) in chunks: + blk, t = lzma_chunk(toks, op) + assert t == cl + mseg.extend(blk) + return bytes(mseg) + + # ---------- F 中承载 seed[soff] 的那个字节的偏移 ---------- + def _f_off_of_seed(self, lay, soff): + """返回 (F 偏移, 该 store 载荷内从 soff 起还剩多少字节)。 + seed_head 超过 64 KiB 时会被切成多片,所以调用方要循环推进。""" + for c in lay["ochunks"]: + if c["kind"] == "store" and c["src"][0] == "seed": + s0 = c["src"][1] + if s0 <= soff < s0 + c["olen"]: + return c["foff"] + 3 + (soff - s0), s0 + c["olen"] - soff + raise RuntimeError("seed 偏移 %d 未被 store 载荷覆盖" % soff) + + def _f_pos_list(self, lay, soff, length): + """seed[soff:soff+length] 在 F 中对应的字节位置列表(可能跨多片)。""" + res = [] + while length > 0: + p, avail = self._f_off_of_seed(lay, soff) + a = min(length, avail) + res.extend(range(p, p + a)) + soff += a + length -= a + return res + + # ---------- 装配 ---------- + def assemble(self, lay): + total, H = lay["total"], lay["H"] + hoff = total - H + C, plant = lay["C"], lay["plant"] + F = bytearray(total) + # pass A: 签名头 + 所有非 store 载荷字节 + F[0:6] = SIG + F[6:8] = VER + struct.pack_into("> i) & 1: + coeff |= 1 << j + coeff ^= 1 << i + eqs.append([coeff, (basevec >> i) & 1]) + for j in range(nb): + p = next((i for i in range(j, nb) 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(nb): + 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(nb): + assert eqs[i][0] == (1 << i) + if eqs[i][1]: + x |= 1 << i + vals = [(x >> (32 * i)) & 0xFFFFFFFF for i in range(len(groups))] + for g in range(len(groups)): + own, mir = groups[g] + b = struct.pack("