**High-level approach**- Use content-addressable storage (CAS) of immutable blobs (code/object files, asset chunks). Store each blob keyed by SHA-256.- Ship patches as manifests + deltas referencing CAS keys. Manifests describe a dependency DAG and ordered operations.- Apply/rollback must be streaming, lock-free where possible, and memory-bounded (fixed buffer).**Data structures**- Blob { hash: bytes32, size: int, type: enum(code|asset|meta) }- Chunk index: sequence of (offset, length, blob_hash)- Manifest { version, ops: [Op], deps: [version hashes], root_hash }- Op = { action: add|replace|delete, target_path, blob_hash, prerequisites: [blob_hash] }Keep a Merkle tree of blobs for fast integrity checks and partial verification.**Chunking & CAS**- Use content-defined chunking (Rabin fingerprint) for assets to maximize reuse across versions.- Chunk size: target 16–64KB average for mobile; tune with asset types (textures bigger).- Store chunks in CAS; patch only needs new chunk hashes + binary diffs for modified blobs.**Binary diffs**- Use bsdiff/xdelta for large binary files; for code, prefer instruction-aware diffs (function-level granularity / symbol table) when possible.- For small assets, reuse chunk-level copy references instead of diffs.**Patch application algorithm (streaming, memory-bounded)**- Validate manifest signature and prerequisite versions.- Topologically sort ops by dependencies; generate ordered list ensuring all prerequisites present.- For each Op (streamed): - Fetch blob/diff stream from server or local cache into fixed-size buffer. - Apply diff to produce output chunks and write directly to target file block-by-block (avoid full-file in-memory). - After finishing each blob, compute hash and compare to expected blob_hash. - Atomically swap pointers: maintain per-file symlink/metadata pointer swap (or swap file header magic + version) to make apply atomic at file granularity.Example pseudo-code:cpp
// streaming apply loop
for op in topo_sorted(manifest.ops):
stream = fetch(op.blob_hash_or_diff)
temp = open_temp(op.target_path)
while data = stream.read(buffer):
temp.write(process_chunk(data))
temp.sync()
if hash(temp) != op.blob_hash: fail_and_fallback()
atomic_rename(temp, op.target_path)
record_applied(op)
**Rollback & failure handling**- Use copy-on-write: write new blobs to new files/paths; only switch live pointers after blob validation.- Maintain an apply-journal: append applied ops with old pointers/hashes. On failure mid-apply: - If failure before final swap for an op → discard temp files; nothing changed. - If failure after some swaps → consult journal to reverse swaps (restore previous pointers).- For low-memory devices, don’t keep full preimage; store only metadata pointers to previous blob hashes. Rollback = atomic pointer swaps back to previous blob hashes.- Support reverse-deltas for space-limited rollback when replacing large files and previous version isn't in CAS: keep reverse-diff for last N versions.**Dependency ordering & safety**- Encode dependencies explicitly (e.g., code module A depends on B). Topologically apply critical runtime order: core engine first, live scripts last.- For runtime consistency, quiesce subsystems when applying dependent code (e.g., pause scripting VM, swap module, resume).- Use feature flags and staged activation: apply blobs inactive, then flip activation bit after all applied.**Trade-offs**- CAS+chunking increases storage overhead but minimizes network patch size and simplifies rollback.- Reverse-diffs increase patch size but speed rollback without full previous blob.- Atomic pointer-swap ensures crash consistency at file granularity; for stronger guarantees use transactional filesystem or small metadata store.This design balances minimal patch size, streaming apply under tight memory, atomic activation, and reliable rollback suitable for games on constrained devices.