- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
- """LZMA2 7z quine 生成器,仅依赖 Python 标准库。
-
- 用法:python blogquine.py 源目录 输出.7z [--quine-name 名称]
- [--src-dir 前缀目录] [--quine-dir 自身所在目录]
- 解压得到源文件及输出归档自身;内容原样存储,辅助信息位于归档内部。
- """
- import argparse
- import binascii
- from dataclasses import dataclass
- import lzma
- import os
- from pathlib import Path
- import struct
- import time
-
- SIG = b"7z\xbc\xaf'\x1c"
- VER = b"\x00\x04"
- CHUNK = 1 << 16 # LZMA2 未压缩 chunk 上限
- MAX_CHUNK_OUTPUT = 1 << 21 # LZMA2 压缩 chunk 解压上限
- MAX_MATCH = 273
- PROPS_BYTE = 0x5D # lc=3, lp=0, pb=2
- FILETIME_EPOCH_OFFSET = 116444736000000000
- PROB_INIT = 1024
-
-
- class RangeEncoder:
- """LZMA 区间编码器;所有整数截断与 SDK 的 UInt32 运算一致。"""
-
- def __init__(self):
- self.low = 0
- self.range = 0xFFFFFFFF
- self.cache = 0
- self.cache_size = 1
- self.buf = bytearray()
-
- def _shift_low(self):
- carry = self.low >> 32
- if (self.low & 0xFFFFFFFF) < 0xFF000000 or carry:
- temp = self.cache
- while self.cache_size:
- self.buf.append((temp + carry) & 0xFF)
- temp = 0xFF
- self.cache_size -= 1
- self.cache = (self.low >> 24) & 0xFF
- self.cache_size += 1
- self.low = (self.low << 8) & 0xFFFFFFFF
-
- def _normalize(self):
- while self.range < 1 << 24:
- self.range = (self.range << 8) & 0xFFFFFFFF
- self._shift_low()
-
- def encode_bit(self, probs, index, bit):
- probability = probs[index]
- bound = (self.range >> 11) * probability
- if bit:
- self.low += bound
- self.range -= bound
- probs[index] -= probability >> 5
- else:
- self.range = bound
- probs[index] += (2048 - probability) >> 5
- self._normalize()
-
- def encode_direct_bits(self, value, bits):
- for shift in range(bits - 1, -1, -1):
- self.range >>= 1
- if value >> shift & 1:
- self.low += self.range
- self._normalize()
-
- def bit_tree(self, probs, offset, bits, symbol, reverse=False):
- node = 1
- shifts = range(bits) if reverse else range(bits - 1, -1, -1)
- for shift in shifts:
- bit = symbol >> shift & 1
- self.encode_bit(probs, offset + node, bit)
- node = (node << 1) | bit
-
- def finish(self):
- for _ in range(5):
- self._shift_low()
- return bytes(self.buf)
-
-
- class LengthEncoder:
- """普通 match 和 rep0 使用独立概率,但共享长度编码逻辑。"""
-
- def __init__(self):
- self.choice = [PROB_INIT] * 2
- self.low = [PROB_INIT] * 32
- self.mid = [PROB_INIT] * 32
- self.high = [PROB_INIT] * 256
-
- def encode(self, rc, pos_state, length):
- value = length - 2
- rc.encode_bit(self.choice, 0, value >= 8)
- if value < 8:
- rc.bit_tree(self.low, pos_state << 3, 3, value)
- else:
- rc.encode_bit(self.choice, 1, value >= 16)
- if value < 16:
- rc.bit_tree(self.mid, pos_state << 3, 3, value - 8)
- else:
- rc.bit_tree(self.high, 0, 8, value - 16)
-
-
- class LzmaEncoder:
- """只编码 match/rep0,编码结果不依赖历史字节,供布局后填充数据。"""
-
- def __init__(self, out_pos=0):
- self.rc = RangeEncoder()
- self.pos = out_pos
- self.state = 0
- self.is_match = [PROB_INIT] * (12 << 4)
- self.is_rep = [PROB_INIT] * 12
- self.is_rep_g0 = [PROB_INIT] * 12
- self.rep0_long = [PROB_INIT] * (12 << 4)
- self.pos_slot = [PROB_INIT] * (4 << 6)
- self.spec_pos = [PROB_INIT] * 114
- self.align = [PROB_INIT] * 16
- self.match_len = LengthEncoder()
- self.rep_len = LengthEncoder()
-
- def _write_dist(self, dist, length):
- if not 1 <= dist <= 0xFFFFFFFF:
- raise ValueError("LZMA distance must be 1..2^32-1")
- value = dist - 1
- bits = max(0, value.bit_length() - 2)
- slot = value if value < 4 else 2 * (bits + 1) + (value >> bits & 1)
- self.rc.bit_tree(self.pos_slot, min(length - 2, 3) << 6, 6, slot)
- if slot >= 4:
- base = (2 | (slot & 1)) << bits
- extra = value - base
- if slot < 14:
- self.rc.bit_tree(self.spec_pos, base - slot - 1, bits, extra, True)
- else:
- self.rc.encode_direct_bits(extra >> 4, bits - 4)
- self.rc.bit_tree(self.align, 0, 4, extra & 15, True)
-
- def match(self, dist, length):
- if not 2 <= length <= MAX_MATCH:
- raise ValueError("LZMA match length must be 2..273")
- self.rc.encode_bit(self.is_match, (self.state << 4) + (self.pos & 3), 1)
- self.rc.encode_bit(self.is_rep, self.state, 0)
- self.match_len.encode(self.rc, self.pos & 3, length)
- self._write_dist(dist, length)
- self.state = 7 if self.state < 7 else 10
- self.pos += length
-
- def rep_match(self, length):
- if not 2 <= length <= MAX_MATCH:
- raise ValueError("LZMA rep0 length must be 2..273")
- index = (self.state << 4) + (self.pos & 3)
- self.rc.encode_bit(self.is_match, index, 1)
- self.rc.encode_bit(self.is_rep, self.state, 1)
- self.rc.encode_bit(self.is_rep_g0, self.state, 0)
- self.rc.encode_bit(self.rep0_long, index, 1)
- self.rep_len.encode(self.rc, self.pos & 3, length)
- self.state = 8 if self.state < 7 else 11
- self.pos += length
-
- def finish(self):
- return self.rc.finish()
-
-
- def crc32(data, value=0):
- return binascii.crc32(data, value) & 0xFFFFFFFF
-
-
- def varint(value):
- """7z UINT64:首字节给出后续小端字节数,最多九字节。"""
- if not 0 <= value < 1 << 64:
- raise ValueError("7z UINT64 out of range")
- for size in range(8):
- if value < 1 << (7 * (size + 1)):
- first = ((0xFF << (8 - size)) & 0xFF) | (value >> (8 * size))
- return bytes([first]) + (value & ((1 << (8 * size)) - 1)).to_bytes(size, "little")
- return b"\xff" + value.to_bytes(8, "little")
-
-
- def store_hdr(payload_len, first=False):
- if not 1 <= payload_len <= CHUNK:
- raise ValueError("LZMA2 stored chunk must contain 1..65536 bytes")
- return bytes([1 if first else 2]) + (payload_len - 1).to_bytes(2, "big")
-
-
- def matches_for(dist, total):
- """固定距离的首个 match 加 rep0;分段时不留下单字节。"""
- if total < 2:
- raise ValueError("match sequence must output at least two bytes")
- tokens = []
- while total:
- length = min(MAX_MATCH, total)
- if total - length == 1:
- length -= 1
- tokens.append(("r0", length) if tokens else ("m", dist, length))
- total -= length
- return tokens
-
-
- def dict_prop_for(maxdist):
- for prop in range(41):
- size = 0xFFFFFFFF if prop == 40 else (2 | (prop & 1)) << (prop // 2 + 11)
- if size >= maxdist:
- return prop, size
- raise ValueError("LZMA2 dictionary distance exceeds 2^32-1")
-
-
- def lzma_chunk(tokens, out_pos):
- """重置状态和概率,保留字典;21-bit 解压长度,不写结束标记。"""
- enc = LzmaEncoder(out_pos)
- for token in tokens:
- if token[0] == "m":
- enc.match(token[1], token[2])
- elif token[0] == "r0":
- enc.rep_match(token[1])
- else:
- raise ValueError("unsupported LZMA token: %r" % (token[0],))
- data = enc.finish()
- total = enc.pos - out_pos
- if not 1 <= total <= MAX_CHUNK_OUTPUT or not 1 <= len(data) <= CHUNK:
- raise ValueError("LZMA2 compressed chunk size out of range")
- unpacked, packed = total - 1, len(data) - 1
- header = (bytes([0xC0 | (unpacked >> 16)])
- + (unpacked & 0xFFFF).to_bytes(2, "big")
- + packed.to_bytes(2, "big") + bytes([PROPS_BYTE]))
- return header + data, total
-
-
- @dataclass(frozen=True)
- class Entry:
- name: str
- is_dir: bool
- data: bytes = b""
-
-
- @dataclass(frozen=True)
- class Layout:
- prefix_size: int
- chunks: tuple
- samples: bytes
- padding: int
- matches: bytes
- header: bytes
- crc_offset: int
- dict_size: int
-
- @property
- def packed_size(self):
- return sum(self.chunks) + 3 * len(self.chunks) + len(self.matches) + 1
-
-
- class LayoutError(RuntimeError):
- """当前填充量没有满足布局或 CRC 定点,换一个填充量重试。"""
-
-
- def split_sizes(size, limit):
- """每片至少两字节,避免 match 无法表达的一字节尾片。"""
- if size < 2:
- raise ValueError("分片总长度必须至少为 2")
- sizes = [limit] * (size // limit)
- tail = size % limit
- if tail == 1:
- sizes[-1] -= 1
- tail = 2
- if tail:
- sizes.append(tail)
- return tuple(sizes)
-
-
- def bitfield(flags):
- data = bytearray((len(flags) + 7) // 8)
- for i, flag in enumerate(flags):
- if flag:
- data[i // 8] |= 1 << (7 - i % 8)
- return bytes(data)
-
-
- def archive_path(value, allow_empty=False):
- value = value.replace("\\", "/")
- if not value and allow_empty:
- return ""
- parts = value.split("/")
- if any(part in ("", ".", "..") for part in parts) or any(c in value for c in ("\0", ":")):
- raise ValueError("归档内路径必须是相对路径,不能包含空段、. 或 ..:%r" % value)
- return "/".join(parts)
-
-
- def encode_segments(segments, out_pos):
- """把 (历史起点, 长度) 转成 match 流;跨 chunk 时保留字典。"""
- result, tokens = bytearray(), []
- used, max_distance = 0, 0
- for source, length in segments:
- for size in split_sizes(length, MAX_CHUNK_OUTPUT):
- if used + size > MAX_CHUNK_OUTPUT:
- block, _ = lzma_chunk(tokens, out_pos)
- result += block
- out_pos += used
- tokens, used = [], 0
- distance = out_pos + used - source
- if distance <= 0:
- raise LayoutError("回引源尚未输出")
- max_distance = max(max_distance, distance)
- tokens.extend(matches_for(distance, size))
- used += size
- source += size
- if tokens:
- block, _ = lzma_chunk(tokens, out_pos)
- result += block
- return bytes(result), max_distance
-
-
- def solve_fixed_crc(data, groups, targets):
- """CRC 是 GF(2) 仿射函数;同步所有镜像,解三个 CRC 的 96-bit 定点。"""
- def vector(values):
- return sum(value << (32 * i) for i, value in enumerate(values))
-
- base = vector(targets())
- columns = []
- for copies in groups:
- for bit in range(32):
- positions = [copy[bit // 8] for copy in copies]
- for pos in positions:
- data[pos] ^= 1 << (bit % 8)
- columns.append(vector(targets()) ^ base)
- for pos in positions:
- data[pos] ^= 1 << (bit % 8)
-
- rows = []
- for bit in range(96):
- coeff = 1 << bit
- for j, column in enumerate(columns):
- coeff ^= ((column >> bit) & 1) << j
- rows.append([coeff, (base >> bit) & 1])
- pivots = []
- for column in range(96):
- rank = len(pivots)
- pivot = next((i for i in range(rank, 96) if rows[i][0] >> column & 1), None)
- if pivot is None:
- continue
- rows[rank], rows[pivot] = rows[pivot], rows[rank]
- for i in range(96):
- if i != rank and rows[i][0] >> column & 1:
- rows[i][0] ^= rows[rank][0]
- rows[i][1] ^= rows[rank][1]
- pivots.append(column)
- if any(not coeff and rhs for coeff, rhs in rows):
- raise LayoutError("CRC 定点方程无解")
- solution = sum(rows[i][1] << column for i, column in enumerate(pivots))
- values = [(solution >> (32 * i)) & 0xFFFFFFFF for i in range(3)]
- for copies, value in zip(groups, values):
- packed = struct.pack("<I", value)
- for copy in copies:
- for i, pos in enumerate(copy):
- data[pos] = packed[i]
- if tuple(values) != targets():
- raise RuntimeError("CRC 定点校验失败")
-
-
- class BlogQuine:
- """单层 LZMA2:解码(P) = content || Q,无独立的辅助文件条目。
-
- Q = V || P || H,V = S32 || H副本 || chunk头样本 || pad || M副本 || 00。
- P 先 store(content || V),再由 M 回引已输出的字节,复现 P || H。
- 内容按原样存储;M 的编码不依赖历史字节,可先求布局,再填头部与 CRC。
- """
-
- def __init__(self, root, quine_name="MayxBlog.7z", src_dir="", exclude=None):
- root = Path(root).resolve()
- if not root.is_dir():
- raise ValueError("源目录不存在:%s" % root)
- self.quine_name = archive_path(quine_name)
- prefix = archive_path(src_dir.strip("/\\"), allow_empty=True)
- excluded = Path(exclude).resolve() if exclude is not None else None
- self.entries, self.files = [], []
- names = {}
-
- def add(name, is_dir, data=b""):
- key = name.casefold()
- if key in names:
- if is_dir and names[key].is_dir and names[key].name == name:
- return
- raise ValueError("归档内路径冲突:%s" % name)
- entry = Entry(name, is_dir, data)
- names[key] = entry
- self.entries.append(entry)
- if not is_dir:
- self.files.append(entry)
-
- def add_parents(name):
- parts = name.split("/")
- for i in range(1, len(parts)):
- add("/".join(parts[:i]), True)
-
- if prefix:
- add_parents(prefix)
- add(prefix, True)
-
- def walk_error(error):
- raise error
-
- for directory, dirs, files in os.walk(root, onerror=walk_error):
- dirs.sort()
- children = [(name, True) for name in dirs]
- children += [(name, False) for name in sorted(files)]
- for name, is_dir in children:
- path = Path(directory) / name
- if not is_dir and path.resolve() == excluded:
- continue
- rel = path.relative_to(root).as_posix()
- name = archive_path(prefix + "/" + rel if prefix else rel)
- add_parents(name)
- add(name, is_dir, b"" if is_dir else path.read_bytes())
-
- add_parents(self.quine_name)
- if self.quine_name.casefold() in names:
- raise ValueError("quine 文件名与源条目冲突:%s" % self.quine_name)
- self.entries.append(Entry(self.quine_name, False))
- self.content = b"".join(entry.data for entry in self.files)
- self.stream_files = [entry for entry in self.files if entry.data]
- self.stream_sizes = b"".join(varint(len(entry.data)) for entry in self.stream_files)
- self.stream_crcs = b"".join(struct.pack("<I", crc32(entry.data)) for entry in self.stream_files)
- self.mtime = time.time_ns() // 100 + FILETIME_EPOCH_OFFSET
-
- def build_header(self, prefix_size, packed_size, archive_size, dict_prop):
- h = bytearray(b"\x01\x04\x06" + varint(prefix_size - 32)
- + b"\x01\x09" + varint(packed_size) + b"\x00\x07\x0b\x01\x00")
- h += b"\x01\x21\x21\x01" + bytes([dict_prop]) # 一个 LZMA2 coder
- h += b"\x0c" + varint(len(self.content) + archive_size) + b"\x00"
- h += b"\x08\x0d" + varint(len(self.stream_files) + 1) + b"\x09" + self.stream_sizes
- h += b"\x0a\x01" + self.stream_crcs
- crc_offset = len(h)
- h += b"\x00" * 4 + b"\x00\x00\x05" + varint(len(self.entries))
-
- def prop(kind, data):
- return bytes([kind]) + varint(len(data)) + data
-
- empty = [entry.is_dir or (not entry.data and i != len(self.entries) - 1)
- for i, entry in enumerate(self.entries)]
- h += prop(0x0E, bitfield(empty))
- if any(flag and not entry.is_dir for flag, entry in zip(empty, self.entries)):
- empty_files = [not entry.is_dir for flag, entry in zip(empty, self.entries) if flag]
- h += prop(0x0F, bitfield(empty_files))
- h += prop(0x14, b"\x01\x00" + struct.pack("<Q", self.mtime) * len(self.entries))
- names = b"".join(entry.name.encode("utf-16-le") + b"\x00\x00" for entry in self.entries)
- attrs = b"".join(struct.pack("<I", 0x10 if entry.is_dir else 0x20) for entry in self.entries)
- h += prop(0x11, b"\x00" + names)
- h += prop(0x15, b"\x01\x00" + attrs)
- return bytes(h + b"\x00\x00"), crc_offset
-
- def layout(self, padding):
- header_size, match_size = 256, 256
- seen = set()
- dc = len(self.content)
- for _ in range(40):
- prefix_size = 32 + header_size + 12 + padding + match_size + 1
- output_size = dc + prefix_size
- chunks = split_sizes(output_size, CHUNK)
- samples = (store_hdr(chunks[0], True) + store_hdr(CHUNK)
- + store_hdr(CHUNK - 1) + store_hdr(chunks[-1]))
- segments, offset = [], 0
- for i, size in enumerate(chunks):
- sample = 0 if i == 0 else 1 if size == CHUNK else 2 if size == CHUNK - 1 else 3
- segments += [(dc + 32 + header_size + 3 * sample, 3), (offset, size)]
- offset += size
- segments += [(dc + 32 + header_size + 12 + padding, match_size + 1),
- (dc + 32, header_size)]
- matches, max_distance = encode_segments(segments, output_size)
- dict_prop, dict_size = dict_prop_for(max_distance)
- packed_size = output_size + 3 * len(chunks) + len(matches) + 1
- header, crc_offset = self.build_header(
- prefix_size, packed_size, prefix_size + packed_size + header_size, dict_prop)
- key = len(header), len(matches)
- if key == (header_size, match_size):
- return Layout(prefix_size, chunks, samples, padding, matches,
- header, crc_offset, dict_size)
- if key in seen:
- break
- seen.add(key)
- header_size, match_size = key
- raise LayoutError("布局没有收敛")
-
- def assemble(self, layout):
- start = struct.pack("<QQI", layout.prefix_size - 32 + layout.packed_size,
- len(layout.header), 0)
- signature = SIG + VER + b"\x00" * 4 + start
- prefix = (signature + layout.header + layout.samples + b"\x00" * layout.padding
- + layout.matches + b"\x00")
- initial = self.content + prefix
- data, offset = bytearray(prefix), 0
- for i, size in enumerate(layout.chunks):
- data += store_hdr(size, first=i == 0) + initial[offset:offset + size]
- offset += size
- data += layout.matches + b"\x00" + layout.header
- if len(prefix) != layout.prefix_size or offset != len(initial):
- raise RuntimeError("装配布局不一致")
-
- def stored_positions(output_offset, size):
- result, consumed, physical = [], 0, layout.prefix_size
- for length in layout.chunks:
- begin = max(output_offset, consumed)
- end = min(output_offset + size, consumed + length)
- result.extend(range(physical + 3 + begin - consumed, physical + 3 + end - consumed))
- consumed += length
- physical += 3 + length
- if len(result) != size:
- raise RuntimeError("无法定位 store 载荷")
- return result
-
- slot = layout.crc_offset
- hoff = layout.prefix_size + layout.packed_size
- groups = [[list(range(32 + slot, 36 + slot)),
- stored_positions(len(self.content) + 32 + slot, 4),
- list(range(hoff + slot, hoff + slot + 4))]]
- for slot in (28, 8):
- groups.append([list(range(slot, slot + 4)), stored_positions(len(self.content) + slot, 4)])
- view = memoryview(data)
- solve_fixed_crc(data, groups, lambda: (crc32(view), crc32(view[hoff:]), crc32(view[12:32])))
- return bytes(data)
-
- def verify(self, data, layout):
- dec = lzma.LZMADecompressor(format=lzma.FORMAT_RAW,
- filters=[{"id": lzma.FILTER_LZMA2, "dict_size": layout.dict_size}])
- expected = len(self.content) + len(data)
- packed = data[layout.prefix_size:layout.prefix_size + layout.packed_size]
- decoded = dec.decompress(packed, max_length=expected + 1)
- view = memoryview(decoded)
- if (not dec.eof or dec.unused_data or len(decoded) != expected
- or view[:len(self.content)] != self.content or view[len(self.content):] != data):
- raise RuntimeError("自解码校验失败")
-
- def build(self):
- last_error = None
- for padding in range(256):
- try:
- layout = self.layout(padding)
- data = self.assemble(layout)
- self.verify(data, layout)
- return data
- except LayoutError as error:
- last_error = error
- raise RuntimeError("无法找到布局/CRC 定点:%s" % last_error)
-
-
- def main():
- parser = argparse.ArgumentParser(description="LZMA2 7z quine 生成器")
- parser.add_argument("srcdir", help="要打包的源目录")
- parser.add_argument("output", help="输出的 7z 文件")
- parser.add_argument("--quine-name", help="归档内自身文件名,默认使用输出文件名")
- parser.add_argument("--src-dir", default="", help="源文件在归档内的前缀目录")
- parser.add_argument("--quine-dir", default="", help="归档内自身文件所在目录")
- args = parser.parse_args()
- try:
- output = Path(args.output)
- name = args.quine_name or output.name
- if args.quine_dir:
- name = archive_path(args.quine_dir.strip("/\\")) + "/" + name
- quine = BlogQuine(args.srcdir, name, args.src_dir, exclude=output)
- data = quine.build() # 验证成功后才写出,失败时保留已有输出。
- output.write_bytes(data)
- except (OSError, ValueError, RuntimeError, lzma.LZMAError) as error:
- parser.exit(1, "blogquine: %s\n" % error)
- print("[blogquine] %d 个文件,%d 个目录,内容 %d 字节" %
- (len(quine.files), sum(entry.is_dir for entry in quine.entries), len(quine.content)))
- print("[blogquine] 已写入 %s:%d 字节;自解码通过" % (output, len(data)))
-
-
- if __name__ == "__main__":
- main()
|