diff options
| -rw-r--r-- | _data/proxylist.yml | 1 | ||||
| -rw-r--r-- | _layouts/default.html | 3 | ||||
| -rw-r--r-- | _posts/2018-10-28-mos-reporter.md | 2 | ||||
| -rw-r--r-- | _posts/2019-06-10-cheat.md | 2 | ||||
| -rw-r--r-- | _posts/2026-10-01-quine.md | 562 | ||||
| -rw-r--r-- | _tools/envs_post-receive | 5 | ||||
| -rw-r--r-- | _tools/serv00_post-receive | 5 | ||||
| -rw-r--r-- | assets/js/offline-page.js | 119 | ||||
| -rw-r--r-- | assets/js/offline-unpack.js | 269 | ||||
| -rw-r--r-- | images/2020.04.06.zip | bin | 4971086 -> 523965 bytes | |||
| -rw-r--r-- | images/GEDSC AIRWAY BILL TO CHINA.jpg | bin | 1113561 -> 0 bytes | |||
| -rw-r--r-- | images/GEDSC AIRWAY BILL TO CHINA.webp | bin | 0 -> 338570 bytes | |||
| -rw-r--r-- | images/MOS_Demo.png | bin | 766232 -> 0 bytes | |||
| -rw-r--r-- | images/MOS_Demo.webp | bin | 0 -> 214122 bytes | |||
| -rw-r--r-- | offline.md | 37 | ||||
| -rw-r--r-- | sw.js | 140 | ||||
| -rw-r--r-- | vendor/libarchive/LICENSE | 21 | ||||
| -rw-r--r-- | vendor/libarchive/wasm-gen/libarchive.js | 15 | ||||
| -rw-r--r-- | vendor/libarchive/wasm-gen/libarchive.wasm | bin | 0 -> 896488 bytes | |||
| -rw-r--r-- | vendor/libarchive/worker-bundle.js | 1 |
20 files changed, 1175 insertions, 7 deletions
diff --git a/_data/proxylist.yml b/_data/proxylist.yml index 4cb705e..35ef0c9 100644 --- a/_data/proxylist.yml +++ b/_data/proxylist.yml @@ -17,7 +17,6 @@ mirrors: - https://mayx.vercel.app/ - https://mayx.netlify.app/ - https://mayx.gitnet.page/ -- https://mayx.stormkit.dev/ - https://mayx.grebedoc.dev/ - https://mayx.codeberg.page/ - https://mayx.tildepages.org/ diff --git a/_layouts/default.html b/_layouts/default.html index 616f962..41c43f7 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -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.7z">Download <strong>7-Zip File</strong></a></li> + <li style="width: 134px;"><a href="/MayxBlog.7z">Download <strong>7-Zip File</strong></a></li> + <li style="width: 134px; border-right: none;"><a href="/offline.html">Manage <strong>Offline Mode</strong></a></li> {% endif %} </ul> </header> diff --git a/_posts/2018-10-28-mos-reporter.md b/_posts/2018-10-28-mos-reporter.md index 0799a9e..baac7f8 100644 --- a/_posts/2018-10-28-mos-reporter.md +++ b/_posts/2018-10-28-mos-reporter.md @@ -20,7 +20,7 @@ tags: [Mayx,MOS] MOS是一个用来解释我不能解释的东西的一个代替品,就如同UFO和人们定义的神一样,因为未知,而又需要一种解释的方法,于是出现了MOS。 *** ## 正文 - + ``` MOS Log System has been Load. MOS Analysis System is Ready. diff --git a/_posts/2019-06-10-cheat.md b/_posts/2019-06-10-cheat.md index 36542bc..708e8fb 100644 --- a/_posts/2019-06-10-cheat.md +++ b/_posts/2019-06-10-cheat.md @@ -139,7 +139,7 @@ Tel +34 612 520330 然后还非常用心的配了一张快递单: - + 注:由于隐私原因,涉及隐私的地方已经被删除 ~~(尽管大家都知道我的手机号)~~ 这个邮件当时也没细看,然后就给Lori Robinson说了我已经给他们发了我的信息,过了几天,她给我发: 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("<Q", self.mtime_ft) * N + h += b"\x14" + varint(len(mt)) + mt + names = bytearray(b"\x00") + 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 + h += b"\x00\x00" + return bytes(h), crc_base + + # ---------- 布局:三层坐标的不动点迭代 ---------- + def layout(self): + content = self.content + dc = len(content) + dict_c = max(1 << 20, 1 << max(20, dc.bit_length())) + C = lzma2_raw(content, dict_c) + self._C = C # 内容压缩只做一次,finalize 复用 + lc = len(C) + # 迭代变量:Y = plant 长度, H = 头部长度, M = Mseg 字节数 + Y, H, M = 96, 256, 256 + seen = set() + for _ in range(200): + ds = lc + 32 + H + Y # seed = C副本‖S32‖H‖plant + d = dc + ds + Ls_LB = split_len(ds, CHUNK) + lLB = 3 * len(Ls_LB) + ds + off_in = lc + lLB + M1 = M + 1 # Mseg ‖ 内层流终止符 0x00 + Ls_M = split_len(M1, CHUNK) + Li = off_in + M1 + self.dict_prop_out, _ = dict_prop_for(max(Li, 1 << 20)) + self.dict_prop_in, _ = dict_prop_for(d + 32 + H + 8192) + st = self._build_streams(C, lc, ds, d, Ls_LB, lLB, off_in, Ls_M, + M1, H) + if st is None: + Y += 8 + continue + mseg = self._build_mseg(st, d, lc, ds, H, M) + if mseg is None: + Y += 8 + continue + M_new = len(mseg) + Y_new = len(st["hdr_bytes"]) + len(st["mech"]) + M_new + 2 + total = st["n"] + 32 + H + file_sizes = [len(x) for _, x in self.files] + sub_sizes = file_sizes + [ds] + [total] + entries = self.entries() + hdr, crc_base = self.build_header(st["n"], total, d, entries, + sub_sizes, Li) + H_new = len(hdr) + key = (Y_new, H_new, M_new) + if key in seen: # 再次见到同一组值 = 已收敛 + Y, H, M = Y_new, H_new, M_new + break + seen.add(key) + Y, H, M = Y_new, H_new, M_new + else: + raise RuntimeError("double layout 不收敛") + return {"Y": Y, "H": H, "M": M} + + # ---------- 定稿:用收敛后的 (Y, H, M) 重算全部结构 ---------- + def finalize(self, Y, H, M): + content = self.content + dc = len(content) + C = self._C # layout 里已压缩过 + lc = len(C) + ds = lc + 32 + H + Y + d = dc + ds + Ls_LB = split_len(ds, CHUNK) + lLB = 3 * len(Ls_LB) + ds + off_in = lc + lLB + M1 = M + 1 + Ls_M = split_len(M1, CHUNK) + Li = off_in + M1 + self.dict_prop_out, ds_out = dict_prop_for(max(Li, 1 << 20)) + self.dict_prop_in, ds_in = dict_prop_for(d + 32 + H + 8192) + st = self._build_streams(C, lc, ds, d, Ls_LB, lLB, off_in, Ls_M, M1, H) + mseg = self._build_mseg(st, d, lc, ds, H, M) + assert len(mseg) == M, "Mseg %d != %d" % (len(mseg), M) + total = st["n"] + 32 + H + file_sizes = [len(x) for _, x in self.files] + sub_sizes = file_sizes + [ds] + [total] + entries = self.entries() + hdr, crc_base = self.build_header(st["n"], total, d, entries, + sub_sizes, Li) + assert len(hdr) == H, "header %d != %d" % (len(hdr), H) + plant = st["hdr_bytes"] + bytes(st["mech"]) + mseg + b"\x00\x00" + assert len(plant) == Y, "plant %d != %d" % (len(plant), Y) + return {"C": C, "lc": lc, "dc": dc, "ds": ds, "d": d, "Li": Li, + "ochunks": st["ochunks"], "hdr_tab": st["hdr_tab"], + "mseg": mseg, "M": M, "H": H, "n": st["n"], "total": total, + "header": hdr, "crc_base": crc_base, "plant": plant, + "dict_size_in": ds_in, "dict_size_out": ds_out, + "n_sub": len(sub_sizes)} + + # ---------- 外层流 P 的构造 ---------- + def _build_streams(self, C, lc, ds, d, Ls_LB, lLB, off_in, Ls_M, M1, H): + hdr_tab, hdr_idx = [], {} + + def hidx(b): + assert len(b) == 3 and b[0] in (0x01, 0x02), b.hex() + if b not in hdr_idx: + hdr_idx[b] = len(hdr_tab) + hdr_tab.append(b) + return hdr_idx[b] + + ochunks = [] + f, io = 32, 0 + mech = bytearray() + + def put_lzma(tokens, ioff): + blk, t = lzma_chunk(tokens, ioff) + ochunks.append({"kind": "lzma", "foff": f, "size": len(blk), + "ioff": ioff, "olen": t, "bytes": blk, + "mech": len(mech)}) + mech.extend(blk) + return len(blk), t + + def put_store(ioff, olen, hi, src): + ochunks.append({"kind": "store", "foff": f, "size": 3 + olen, + "ioff": ioff, "olen": olen, "hdr": hi, "src": src}) + return 3 + olen + + # 段 1:store 链承载 Cseg(I[0:lc] = C 的字节;源 = seed 的 C 参照副本) + Ls_C = split_len(lc, CHUNK) + c_off = 0 + for L in Ls_C: + f += put_store(io, L, hidx(store_hdr(L, first=(c_off == 0))), + ("seed", c_off)) + io += L + c_off += L + assert io == lc + + # 段 2:LBseg(I[lc : lc+lLB])—— 内层 store 链,载荷 = seed + lb_off = lc + seed_off = 0 + for j, L in enumerate(Ls_LB): + lb_hdr = store_hdr(L, first=False) + if j == 0 or L != CHUNK: + # 没有可回引的相同样本,用外层 store chunk 原样携带这 3 字节 + f += put_store(lb_off, 3, hidx(store_hdr(3, first=False)), + ("tab", hidx(lb_hdr))) + io += 3 + else: + # 与 chunk0 的头逐字节相同,直接从 I[lc:lc+3] 复制 + n, t = put_lzma([("m", lb_off - lc, 3)], lb_off) + assert t == 3 + f += n + io += 3 + # 载荷:C 参照副本区走 rep0 回溯匹配(去重发生处),其余走 store + pay = lb_off + 3 + i = 0 + while i < L: + if seed_off + i < lc: + a = min(L - i, lc - (seed_off + i), LZ2_MAXU) + dist = (pay + i) - (seed_off + i) + n, t = put_lzma(matches_for(dist, a), pay + i) + assert t == a + f += n + io += a + i += a + else: + a = min(L - i, LZ2_MAXU) + f += put_store(pay + i, a, hidx(store_hdr(a, first=False)), + ("seed", seed_off + i)) + io += a + i += a + lb_off += 3 + L + seed_off += L + assert lb_off == lc + lLB and io == lc + lLB + + # 段 3:store 链承载 Mseg ‖ 0x00(0x00 是内层流的终止符) + m_off = 0 + for L in Ls_M: + f += put_store(off_in + m_off, L, + hidx(store_hdr(L, first=False)), ("mseg", m_off)) + io += L + m_off += L + # 外层流自己的终止符 0x00 + ochunks.append({"kind": "term", "foff": f, "size": 1, "ioff": io, + "olen": 0, "bytes": b"\x00"}) + f += 1 + return {"ochunks": ochunks, "mech": mech, "hdr_tab": hdr_tab, + "hdr_bytes": b"".join(hdr_tab), "n": f - 32} + + # ---------- 内层 Mseg:把 quine 区的每个 F 区段映射成 match 链 ---------- + def _build_mseg(self, st, d, lc, ds, H, M): + dc = d - ds + s_S32 = dc + lc # seed 里 S32 副本 + s_H = dc + lc + 32 # seed 里 H 副本 + s_tab = dc + lc + 32 + H # seed 里头样本表 + s_mech = s_tab + len(st["hdr_bytes"]) # seed 里机械串 + s_mseg = s_mech + len(st["mech"]) # seed 里 Mseg 副本 + total = st["n"] + 32 + H + hoff = 32 + st["n"] + segs = [(0, 32, s_S32)] # F[0:32) 签名头 + for c in st["ochunks"]: + if c["kind"] == "store": + segs.append((c["foff"], 3, s_tab + 3 * c["hdr"])) + src = c["src"] + if src[0] == "seed": + u = dc + src[1] + elif src[0] == "tab": + u = s_tab + 3 * src[1] + else: # ("mseg", off) + u = s_mseg + src[1] + segs.append((c["foff"] + 3, c["olen"], u)) + elif c["kind"] == "lzma": + segs.append((c["foff"], c["size"], s_mech + c["mech"])) + else: # term:并入前一个载荷段 + pf, pl, pu = segs[-1] + segs[-1] = (pf, pl + 1, pu) + segs.append((hoff, H, s_H)) # F[hoff:] 头部 + pos = 0 + for (fo, ln, u) in segs: + assert fo == pos, "段不连续: %d != %d" % (fo, pos) + assert ln >= 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("<Q", F, 12, lay["n"]) + struct.pack_into("<Q", F, 20, H) + for c in lay["ochunks"]: + if c["kind"] == "store": + F[c["foff"]:c["foff"] + 3] = lay["hdr_tab"][c["hdr"]] + else: + F[c["foff"]:c["foff"] + c["size"]] = c["bytes"] + F[hoff:hoff + H] = lay["header"] + # pass B: seed = C副本 ‖ S32副本 ‖ H副本 ‖ plant + seed = bytearray() + seed += C + seed += bytes(F[0:32]) + seed += bytes(F[hoff:hoff + H]) + seed += plant + assert len(seed) == lay["ds"], "seed %d != %d" % (len(seed), lay["ds"]) + # pass C: 各 store 的载荷 + msegz = lay["mseg"] + b"\x00" + for c in lay["ochunks"]: + if c["kind"] != "store": + continue + p = c["foff"] + 3 + src, L = c["src"], c["olen"] + if src[0] == "seed": + F[p:p + L] = seed[src[1]:src[1] + L] + elif src[0] == "tab": + F[p:p + 3] = lay["hdr_tab"][src[1]] + else: + F[p:p + L] = msegz[src[1]:src[1] + L] + return F, bytes(seed) + + # ---------- CRC 定点(128x128 GF(2):seed/quine/Next/Start 四个 CRC) ---------- + def solve_crc(self, F, lay, C, plant): + H, hoff = lay["H"], lay["total"] - lay["H"] + n_sub, crc_base = lay["n_sub"], lay["crc_base"] + lc = lay["lc"] + # seed 里 S32 / H 副本在 F 中的镜像(可能跨多片,逐字节列出) + scopy0 = self._f_pos_list(lay, lc, 32) + Fv = memoryview(F) + + def seed_crc(): + v = crc32(C) + v = crc32(Fv[0:32], v) # S32 副本 + v = crc32(Fv[hoff:hoff + H], v) # H 副本 + return crc32(plant, v) + + def targets(): + return (seed_crc(), crc32(Fv), crc32(Fv[hoff:hoff + H]), + crc32(Fv[12:32])) + + # 已知:各内容文件的 CRC,写本体 + seed 里 H 副本的镜像两处 + known = [crc32(data) for _, data in self.files] + for i, e in enumerate(known): + slot = crc_base + 4 * i + b = struct.pack("<I", e) + for k in range(4): + F[hoff + slot + k] = b[k] + mir = self._f_pos_list(lay, lc + 32 + slot, 4) + assert len(mir) == 4 + for k in range(4): + F[mir[k]] = b[k] + # 未知量:D_seed(seed 的 CRC)、D(quine 的 CRC)、N、S + # 每个未知量 4 字节,每字节有"本体 + seed 镜像"两个落点 + qs = crc_base + 4 * len(known) # seed 子流的 CRC 槽 + q = crc_base + 4 * (n_sub - 1) # quine 子流的 CRC 槽 + groups = [] + for slot in (qs, q): + groups.append([list(range(hoff + slot, hoff + slot + 4)), + self._f_pos_list(lay, lc + 32 + slot, 4)]) + groups.append([list(range(28, 32)), scopy0[28:32]]) # NextHeaderCRC + groups.append([list(range(8, 12)), scopy0[8:12]]) # StartHeaderCRC + nb = 32 * len(groups) + base = targets() + basevec = 0 + for i, v in enumerate(base): + basevec |= v << (32 * i) + cols = [] + for g in range(len(groups)): + own, mir = groups[g] + for kb in range(32): + bi, bb = kb // 8, kb % 8 + poss = [own[bi], mir[bi]] + for p in poss: + F[p] ^= (1 << bb) + t = targets() + for p in poss: + F[p] ^= (1 << bb) + delta = 0 + for i, v in enumerate(t): + delta |= (v ^ base[i]) << (32 * i) + cols.append(delta) + eqs = [] + for i in range(nb): + coeff = 0 + for j in range(nb): + if (cols[j] >> 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("<I", vals[g]) + for k in range(4): + F[own[k]] = b[k] + F[mir[k]] = b[k] + t = targets() + assert t == tuple(vals), "CRC 定点失败: %s != %s" % (t, vals) + return vals + + def build(self): + l0 = self.layout() + lay = self.finalize(l0["Y"], l0["H"], l0["M"]) + F, seed = self.assemble(lay) + crcs = self.solve_crc(F, lay, lay["C"], lay["plant"]) + # CRC 定点会改写 H / S32,而 seed 是它们的镜像,定点后需重建 + hoff = lay["total"] - lay["H"] + seed = lay["C"] + bytes(F[0:32]) + bytes(F[hoff:hoff + lay["H"]]) \ + + lay["plant"] + return bytes(F), lay, seed, crcs + + +def verify_double(F, lay, files, seed, content): + """串联两个原始 LZMA2 解码器还原 U,逐段校验。""" + d, total = lay["d"], lay["total"] + dec_o = lzma.LZMADecompressor( + format=lzma.FORMAT_RAW, + filters=[{"id": lzma.FILTER_LZMA2, "dict_size": lay["dict_size_out"]}]) + I = dec_o.decompress(F[32:32 + lay["n"]]) + ok = True + if len(I) != lay["Li"]: + print("[verify] 外层输出 %d != I %d" % (len(I), lay["Li"])) + return False + dec_i = lzma.LZMADecompressor( + format=lzma.FORMAT_RAW, + filters=[{"id": lzma.FILTER_LZMA2, "dict_size": lay["dict_size_in"]}]) + U = dec_i.decompress(I) + exp = d + total + if len(U) != exp: + print("[verify] 内层输出 %d != %d" % (len(U), exp)) + return False + dc, ds = lay["dc"], lay["ds"] + if U[:dc] != content: + print("[verify] 内容不匹配") + ok = False + pos = 0 + for rel, data in files: # 内容文件排在 U 的最前面 + if U[pos:pos + len(data)] != data: + print("[verify] 文件不匹配:", rel) + ok = False + pos += len(data) + assert pos == dc + if U[dc:dc + ds] != seed: + print("[verify] seed 不匹配") + ok = False + if U[d:] != F: + print("[verify] quine 自复制不匹配") + ok = False + print("[verify] 自解码: |I|=%d, |U|=%d, seed/文件/quine 全部匹配: %s" + % (len(I), len(U), ok)) + return ok +``` + 仅仅是大佬的一句话,AI就能做出来,实在是太可怕了。最终我试了一下,官方的7-Zip可以正常解压,但是Windows资源管理器不行……Windows资源管理器使用的也是[libarchive](https://github.com/libarchive/libarchive),似乎是因为libarchive只支持2个解码器,并且[第二个解码器只能是像BCJ这样的过滤器](https://github.com/libarchive/libarchive/blob/8bb3bbdc7b117a1e22086a2260f2087aafa90687/libarchive/archive_read_support_format_7zip.c#L1475)……明明7-Zip官方是可以解压的,不知道如果把这个问题当作BUG报告给libarchive他们会不会解决?但目前存在兼容性问题的话我博客压缩包就先不用这个方案了吧🥲…… ## 对TXZ格式的尝试 在做完7z格式的压缩包之后,我发现了一个问题,虽然7z确实很流行,但是在Linux下解压起来有点麻烦,7-Zip历史上主要面向Windows,Linux上长期以来更多依赖p7zip等第三方移植,因此生态集成度不如tar.xz,想要解压7z文件还得额外安装。 不过Linux下也有个支持LZMA2算法的压缩软件,那就是前些年出过[后门](https://tukaani.org/xz-backdoor/)的XZ Utils,配合tar就可以做出TXZ(tar.xz)文件,甚至用我[博客终端](https://mabbs.github.io/linux/)中的BusyBox也能解压。我想了一下反正有AI,干脆一句话让AI帮我把blogquine.py改成tar.xz格式的好了,结果倒也没费多少功夫,AI就这样写出来了: diff --git a/_tools/envs_post-receive b/_tools/envs_post-receive index fa0f7c6..1df73b4 100644 --- a/_tools/envs_post-receive +++ b/_tools/envs_post-receive @@ -7,7 +7,8 @@ mkdir Mabbs curl -L -o Mabbs/README.md https://github.com/Mabbs/Mabbs/raw/main/README.md bundle exec jekyll build -d ../public_html python3 _tools/blogquine.py --src-dir blog --quine-dir blog ../public_html/ MayxBlog.7z -mv MayxBlog.7z ../public_html/ +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 @@ -18,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
\ No newline at end of file diff --git a/_tools/serv00_post-receive b/_tools/serv00_post-receive index f0192dc..ab45da0 100644 --- a/_tools/serv00_post-receive +++ b/_tools/serv00_post-receive @@ -7,18 +7,21 @@ 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/ 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/ +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 lfs track "*.wasm" git branch -m main echo "--- title: Mayx diff --git a/assets/js/offline-page.js b/assets/js/offline-page.js new file mode 100644 index 0000000..8b0bdb1 --- /dev/null +++ b/assets/js/offline-page.js @@ -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 = |
