diff --git a/crates/engine/src/io.rs b/crates/engine/src/io.rs
--- a/crates/engine/src/io.rs
+++ b/crates/engine/src/io.rs
@@ -12,9 +12,9 @@ pub fn read_file(path: &str) -> Result<Vec<u8>, String> {
     if !meta.is_file() {
         return Err(format!("{path}: not a regular file"));
     }
-    const MAX_FILE_SIZE: u64 = 2 << 30; // 2 GiB
+    const MAX_FILE_SIZE: u64 = 16 << 30; // 16 GiB
     if meta.len() > MAX_FILE_SIZE {
-        return Err(format!("{path}: file size {} exceeds 2 GiB limit", meta.len()));
+        return Err(format!("{path}: file size {} exceeds 16 GiB limit", meta.len()));
     }
     std::fs::read(path).map_err(|e| format!("{path}: {e}"))
 }
diff --git a/crates/engine/src/recovery.rs b/crates/engine/src/recovery.rs
--- a/crates/engine/src/recovery.rs
+++ b/crates/engine/src/recovery.rs
@@ -105,7 +105,7 @@ fn write_atomic(path: &Path, bytes: &[u8]) -> Result<()> {
 /// full of entries can't stall start-up.
 pub const MAX_REOPEN: usize = 32;
 /// Largest entry read back.
-const MAX_ENTRY_BYTES: u64 = 2 << 30;
+const MAX_ENTRY_BYTES: u64 = 16 << 30;
 const MAX_META_BYTES: u64 = 64 << 10;
 
 /// `p` as bytes when it's a regular file no bigger than `max`: reading a FIFO left where an
diff --git a/crates/format/src/lib.rs b/crates/format/src/lib.rs
--- a/crates/format/src/lib.rs
+++ b/crates/format/src/lib.rs
@@ -24,9 +24,9 @@ pub const EXTENSION: &str = "deckcraft";
 /// Format version written into the manifest.
 pub const VERSION: u32 = 1;
 /// Largest entry we will inflate (guards against zip bombs).
-const MAX_ENTRY: u64 = 2 << 30;
+const MAX_ENTRY: u64 = 8 << 30;
 /// All embedded media and chart sidecars together, inflated (#393).
-const MAX_TOTAL_MEDIA: u64 = 4 << 30;
+const MAX_TOTAL_MEDIA: u64 = 16 << 30;
 /// Media items and chart sidecar parts one file may list.
 const MAX_MEDIA_ITEMS: usize = 100_000;
 
@@ -79,12 +79,14 @@ pub fn save(p: &Presentation) -> Result<Vec<u8>> {
                 continue;
             }
             // Already-compressed media is stored, not deflated again.
-            let opts = if is_compressed(&item.content_type) { stored } else { deflate };
+            let large = item.data.len() as u64 >= u32::MAX as u64 / 2;
+            let opts = if is_compressed(&item.content_type) { stored } else { deflate }.large_file(large);
             z.start_file(format!("media/{}", item.id.0), opts).map_err(w)?;
             z.write_all(&item.data).map_err(|e| FormatError::Write(e.to_string()))?;
         }
         for (name, content_type, bytes) in parts {
-            let opts = if is_compressed(&content_type) { stored } else { deflate };
+            let large = bytes.len() as u64 >= u32::MAX as u64 / 2;
+            let opts = if is_compressed(&content_type) { stored } else { deflate }.large_file(large);
             z.start_file(name, opts).map_err(w)?;
             z.write_all(&bytes).map_err(|e| FormatError::Write(e.to_string()))?;
         }
diff --git a/crates/pptx/src/opc.rs b/crates/pptx/src/opc.rs
--- a/crates/pptx/src/opc.rs
+++ b/crates/pptx/src/opc.rs
@@ -7,11 +7,11 @@ use crate::PptxError;
 use crate::xml::{self, A, W};
 
 /// Largest single part we decompress (media included).
-pub const MAX_PART: u64 = 512 * 1024 * 1024;
+pub const MAX_PART: u64 = 8 * 1024 * 1024 * 1024;
 /// Largest XML part we parse.
 pub const MAX_XML_PART: usize = 96 * 1024 * 1024;
 /// Total decompressed bytes accepted for one package.
-pub const MAX_TOTAL: u64 = 2 * 1024 * 1024 * 1024;
+pub const MAX_TOTAL: u64 = 16 * 1024 * 1024 * 1024;
 /// Total bytes we are willing to decompress for one package, counting parts we then discard
 /// (too large, or failing part-way): bounds inflate work, not just what is kept.
 pub const MAX_INFLATE_WORK: u64 = 2 * MAX_TOTAL;
