1use std::{collections::HashSet, sync::LazyLock};
2
3pub const DLC_PACK_MAGIC: &[u8; 4] = b"BDLP";
5
6pub const DLC_PACK_VERSION_LATEST: u8 = 4;
8
9pub const DEFAULT_BLOCK_SIZE: usize = 10 * 1024 * 1024;
11
12pub const FORBIDDEN_EXTENSIONS: &[&str] = &[
16 "7z", "accda", "accdb", "accde", "accdr", "ace", "ade", "adp", "app", "appinstaller", "application", "appref", "appx", "appxbundle", "arj", "asax", "asd", "ashx", "asp", "aspx", "b64", "bas", "bat", "bgi", "bin", "btm", "bz", "bz2", "bzip", "bzip2", "cab", "cer", "cfg", "chi", "chm", "cla", "class", "cmd", "com", "cpi", "cpio", "cpl", "crt", "crx", "csh", "der", "desktopthemefile", "diagcab", "diagcfg", "diagpkg", "dll", "dmg", "doc", "docm", "docx", "dotm", "drv", "eml", "exe", "fon", "fxp", "gadget", "grp", "gz", "gzip", "hlp", "hta", "htc", "htm", "html", "htt", "ics", "img", "ini", "ins", "inx", "iqy", "iso", "isp", "isu", "jar", "jnlp", "job", "js", "jse", "ksh", "lha", "lnk", "local", "lz", "lzh", "lzma", "mad", "maf", "mag", "mam", "manifest", "maq", "mar", "mas", "mat", "mav", "maw", "mda", "mdb", "mde", "mdt", "mdw", "mdz", "mht", "mhtml", "mmc", "msc", "msg", "msh", "msh1", "msh1xml", "msh2", "msh2xml", "mshxml", "msi", "msix", "msixbundle", "msm", "msp", "mst", "msu", "ocx", "odt", "one", "onepkg", "onetoc", "onetoc2", "ops", "oxps", "oxt", "paf", "partial", "pcd", "pdf", "pif", "pl", "plg", "pol", "potm", "ppam", "ppkg", "ppsm", "ppt", "pptm", "pptx", "prf", "prg", "ps1", "ps1xml", "ps2", "ps2xml", "psc1", "psc2", "psm1", "pst", "r00", "r01", "r02", "r03", "rar", "reg", "rels", "rev", "rgs", "rpm", "rtf", "scf", "scr", "sct", "search", "settingcontent", "settingscontent", "sh", "shb", "sldm", "slk", "svg", "swf", "sys", "tar", "tbz", "tbz2", "tgz", "tlb", "url", "uue", "vb", "vbe", "vbs", "vbscript", "vdx", "vhd", "vhdx", "vsmacros", "vss", "vssm", "vssx", "vst", "vstm", "vstx", "vsw", "vsx", "vtx", "wbk", "webarchive", "website", "wml", "ws", "wsc", "wsf", "wsh", "xar", "xbap", "xdp", "xlam", "xll", "xlm", "xls", "xlsb", "xlsm", "xlsx", "xltm", "xlw", "xml", "xnk", "xps", "xrm", "xsd", "xsl", "xxe", "xz", "z", "zip",
17];
18
19static FORBIDDEN_SET: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
21 FORBIDDEN_EXTENSIONS.iter().copied().collect()
22});
23
24pub fn is_forbidden_extension(ext: &str) -> bool {
26 let lowercase = ext.trim_start_matches('.').to_ascii_lowercase();
27 FORBIDDEN_SET.contains(lowercase.as_str())
28}
29
30pub fn is_data_executable(data: &[u8]) -> bool {
42 if infer::is_app(data) {
43 return true;
44 }
45 if data.starts_with(b"#!") {
46 return true;
47 }
48 false
49}
50
51#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
53pub struct ManifestEntry {
54 pub path: String,
55 #[serde(skip_serializing_if = "Option::is_none")]
56 pub original_extension: Option<String>,
57 #[serde(skip_serializing_if = "Option::is_none")]
58 pub type_path: Option<String>,
59}
60
61impl ManifestEntry {
62 pub fn from_pack_item(item: &crate::PackItem) -> Self {
63 ManifestEntry {
64 path: item.path.clone(),
65 original_extension: item
66 .original_extension
67 .clone()
68 .filter(|s| !s.is_empty()),
69 type_path: item.type_path.clone(),
70 }
71 }
72}
73
74#[derive(Clone, Debug)]
77pub struct V4ManifestEntry {
78 pub path: String,
79 pub original_extension: String,
80 pub type_path: Option<String>,
81 pub block_id: u32,
83 pub block_offset: u32,
85 pub size: u32,
87}
88
89impl V4ManifestEntry {
90 pub fn from_pack_item(item: &crate::PackItem, block_id: u32, block_offset: u32) -> Self {
91 V4ManifestEntry {
92 path: item.path.clone(),
93 original_extension: item.original_extension.clone().unwrap_or_default(),
94 type_path: item.type_path.clone(),
95 block_id,
96 block_offset,
97 size: item.plaintext.len() as u32,
98 }
99 }
100
101 #[allow(dead_code)]
103 fn write_binary<W: std::io::Write>(&self, writer: &mut PackWriter<W>) -> std::io::Result<()> {
104 writer.write_u32(self.path.len() as u32)?;
105 writer.write_bytes(self.path.as_bytes())?;
106
107 writer.write_u8(self.original_extension.len() as u8)?;
108 writer.write_bytes(self.original_extension.as_bytes())?;
109
110 if let Some(ref tp) = self.type_path {
111 writer.write_u16(tp.len() as u16)?;
112 writer.write_bytes(tp.as_bytes())?;
113 } else {
114 writer.write_u16(0)?;
115 }
116
117 writer.write_u32(self.block_id)?;
118 writer.write_u32(self.block_offset)?;
119 writer.write_u32(self.size)
120 }
121
122 #[allow(dead_code)]
124 fn read_binary<R: std::io::Read>(reader: &mut PackReader<R>) -> std::io::Result<Self> {
125 let path_len = reader.read_u32()? as usize;
126 let path = String::from_utf8(reader.read_bytes(path_len)?)
127 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
128
129 let ext_len = reader.read_u8()? as usize;
130 let original_extension = if ext_len > 0 {
131 String::from_utf8(reader.read_bytes(ext_len)?)
132 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?
133 } else {
134 String::new()
135 };
136
137 let type_len = reader.read_u16()? as usize;
138 let type_path = if type_len > 0 {
139 let tp = String::from_utf8(reader.read_bytes(type_len)?)
140 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
141 Some(tp)
142 } else {
143 None
144 };
145
146 let block_id = reader.read_u32()?;
147 let block_offset = reader.read_u32()?;
148 let size = reader.read_u32()?;
149
150 Ok(V4ManifestEntry {
151 path,
152 original_extension,
153 type_path,
154 block_id,
155 block_offset,
156 size,
157 })
158 }
159}
160
161#[derive(Clone, Debug)]
163pub struct BlockMetadata {
164 pub block_id: u32,
165 pub file_offset: u64, pub encrypted_size: u32, pub uncompressed_size: u32, pub nonce: [u8; 12], pub crc32: u32, }
171
172impl BlockMetadata {
173 #[allow(dead_code)]
174 fn write_binary<W: std::io::Write>(&self, writer: &mut PackWriter<W>) -> std::io::Result<()> {
175 writer.write_u32(self.block_id)?;
176 writer.write_u64(self.file_offset)?;
177 writer.write_u32(self.encrypted_size)?;
178 writer.write_u32(self.uncompressed_size)?;
179 writer.write_bytes(&self.nonce)?;
180 writer.write_u32(self.crc32)
181 }
182
183 #[allow(dead_code)]
184 fn read_binary<R: std::io::Read>(reader: &mut PackReader<R>) -> std::io::Result<Self> {
185 let block_id = reader.read_u32()?;
186 let file_offset = reader.read_u64()?;
187 let encrypted_size = reader.read_u32()?;
188 let uncompressed_size = reader.read_u32()?;
189 let nonce = reader.read_nonce()?;
190 let crc32 = reader.read_u32()?;
191
192 Ok(BlockMetadata {
193 block_id,
194 file_offset,
195 encrypted_size,
196 uncompressed_size,
197 nonce,
198 crc32,
199 })
200 }
201}
202
203pub(crate) struct PackReader<R: std::io::Read> {
211 inner: R,
212}
213
214impl<R: std::io::Read> PackReader<R> {
215 pub fn new(inner: R) -> Self {
216 Self { inner }
217 }
218
219 pub fn read_u8(&mut self) -> std::io::Result<u8> {
221 let mut buf = [0u8; 1];
222 self.inner.read_exact(&mut buf)?;
223 Ok(buf[0])
224 }
225
226 #[allow(dead_code)]
229 pub fn read_and_decrypt(
230 &mut self,
231 key: &crate::EncryptionKey,
232 len: usize,
233 nonce: &[u8],
234 ) -> Result<Vec<u8>, DlcError> {
235 let ciphertext = self.read_bytes(len)?;
236
237 let cursor = std::io::Cursor::new(ciphertext);
238 crate::pack_format::decrypt_with_key(key, cursor, nonce)
239 }
240
241 pub fn read_u16(&mut self) -> std::io::Result<u16> {
242 let mut buf = [0u8; 2];
243 self.inner.read_exact(&mut buf)?;
244 Ok(u16::from_be_bytes(buf))
245 }
246
247 pub fn read_u32(&mut self) -> std::io::Result<u32> {
248 let mut buf = [0u8; 4];
249 self.inner.read_exact(&mut buf)?;
250 Ok(u32::from_be_bytes(buf))
251 }
252
253 #[allow(dead_code)]
254 pub fn read_u64(&mut self) -> std::io::Result<u64> {
255 let mut buf = [0u8; 8];
256 self.inner.read_exact(&mut buf)?;
257 Ok(u64::from_be_bytes(buf))
258 }
259
260 pub fn read_bytes(&mut self, len: usize) -> std::io::Result<Vec<u8>> {
261 let mut buf = vec![0u8; len];
262 self.inner.read_exact(&mut buf)?;
263 Ok(buf)
264 }
265
266 pub fn read_string_u16(&mut self) -> std::io::Result<String> {
267 let len = self.read_u16()? as usize;
268 self.read_string_internal(len)
269 }
270
271 pub fn read_string_u8(&mut self) -> std::io::Result<String> {
272 let len = self.read_u8()? as usize;
273 self.read_string_internal(len)
274 }
275
276 fn read_string_internal(&mut self, len: usize) -> std::io::Result<String> {
277 let bytes = self.read_bytes(len)?;
278 String::from_utf8(bytes)
281 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
282 }
283
284 pub fn read_nonce(&mut self) -> std::io::Result<[u8; 12]> {
285 let mut nonce = [0u8; 12];
286 self.inner.read_exact(&mut nonce)?;
287 Ok(nonce)
288 }
289}
290
291impl<R: std::io::Read + std::io::Seek> PackReader<R> {
293 pub fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
295 self.inner.seek(pos)
296 }
297}
298
299pub(crate) struct PackWriter<W: std::io::Write> {
301 inner: W,
302}
303
304impl<W: std::io::Write> PackWriter<W> {
305 pub fn new(inner: W) -> Self {
306 Self { inner }
307 }
308
309 #[allow(dead_code)]
312 pub fn write_encrypted(
313 &mut self,
314 key: &crate::EncryptionKey,
315 nonce: &[u8],
316 plaintext: &[u8],
317 ) -> Result<(), DlcError> {
318 use aes_gcm::{Aes256Gcm, KeyInit, Nonce, aead::Aead};
319 let cipher = key.with_secret(|kb| {
320 Aes256Gcm::new_from_slice(kb).map_err(|e| DlcError::CryptoError(e.to_string()))
321 })?;
322 let ct = cipher
323 .encrypt(Nonce::from_slice(nonce), plaintext)
324 .map_err(|_| DlcError::EncryptionFailed("block encryption failed".into()))?;
325 self.write_bytes(&ct).map_err(|e| DlcError::Other(e.to_string()))
326 }
327
328 pub fn write_u8(&mut self, val: u8) -> std::io::Result<()> {
329 self.inner.write_all(&[val])
330 }
331
332 pub fn write_u16(&mut self, val: u16) -> std::io::Result<()> {
333 self.inner.write_all(&val.to_be_bytes())
334 }
335
336 pub fn write_u32(&mut self, val: u32) -> std::io::Result<()> {
337 self.inner.write_all(&val.to_be_bytes())
338 }
339
340 pub fn write_u64(&mut self, val: u64) -> std::io::Result<()> {
341 self.inner.write_all(&val.to_be_bytes())
342 }
343
344 pub fn write_bytes(&mut self, bytes: &[u8]) -> std::io::Result<()> {
345 self.inner.write_all(bytes)
346 }
347
348 pub fn write_string_u16(&mut self, s: &str) -> std::io::Result<()> {
349 let bytes = s.as_bytes();
350 self.write_u16(bytes.len() as u16)?;
351 self.write_bytes(bytes)
352 }
353
354 pub fn finish(mut self) -> std::io::Result<W> {
355 self.inner.flush()?;
356 Ok(self.inner)
357 }
358}
359
360struct PackHeader {
362 version: u8,
363 product: String,
364 dlc_id: String,
365}
366
367impl PackHeader {
368 fn read<R: std::io::Read>(reader: &mut PackReader<R>) -> std::io::Result<Self> {
369 let magic = reader.read_bytes(4)?;
370 if magic != DLC_PACK_MAGIC {
371 return Err(std::io::Error::new(
372 std::io::ErrorKind::InvalidData,
373 "invalid dlcpack magic",
374 ));
375 }
376
377 let version = reader.read_u8()?;
378 let mut product = String::new();
379
380 if version == 3 || version == 4 {
381 product = reader.read_string_u16()?;
382 } else if version > 4 {
383 return Err(std::io::Error::new(
384 std::io::ErrorKind::InvalidData,
385 format!("unsupported pack version: {}", version),
386 ));
387 }
388
389 let dlc_id = reader.read_string_u16()?;
390
391 Ok(PackHeader {
392 version,
393 product,
394 dlc_id,
395 })
396 }
397
398 fn write<W: std::io::Write>(&self, writer: &mut PackWriter<W>) -> std::io::Result<()> {
399 writer.write_bytes(DLC_PACK_MAGIC)?;
400 writer.write_u8(self.version)?;
401
402 if self.version == 3 || self.version == 4 {
403 writer.write_string_u16(&self.product)?;
404 }
405
406 writer.write_string_u16(&self.dlc_id)
407 }
408}
409
410use aes_gcm::{Aes256Gcm, KeyInit, Nonce, aead::AeadInPlace};
416use secure_gate::ExposeSecret;
417
418use crate::{DlcError, DlcId, EncryptionKey, PackItem, Product};
419
420pub(crate) fn decrypt_with_key<R: std::io::Read>(
421 key: &crate::EncryptionKey,
422 mut reader: R,
423 nonce: &[u8],
424) -> Result<Vec<u8>, DlcError> {
425 let mut buf = Vec::new();
428 reader
429 .read_to_end(&mut buf)
430 .map_err(|e| DlcError::Other(e.to_string()))?;
431
432 key.with_secret(|key_bytes| {
433 if key_bytes.len() != 32 {
434 return Err(DlcError::InvalidEncryptKey(
435 "encrypt key must be 32 bytes (AES-256)".into(),
436 ));
437 }
438 if nonce.len() != 12 {
439 return Err(DlcError::InvalidNonce(
440 "nonce must be 12 bytes (AES-GCM)".into(),
441 ));
442 }
443 let cipher = Aes256Gcm::new_from_slice(key_bytes)
444 .map_err(|e| DlcError::CryptoError(e.to_string()))?;
445 let nonce = Nonce::from_slice(nonce);
446 cipher
448 .decrypt_in_place(nonce, &[], &mut buf)
449 .map_err(|_|
450 DlcError::DecryptionFailed(
451 "authentication failed (incorrect key or corrupted ciphertext)".to_string(),
452 )
453 )
454 })?;
455 Ok(buf)
456}
457
458#[derive(Debug, Clone, Copy)]
461pub enum CompressionLevel {
462 Fast,
464 Default,
466 Best,
468}
469
470impl From<CompressionLevel> for flate2::Compression {
471 fn from(level: CompressionLevel) -> Self {
472 match level {
473 CompressionLevel::Fast => flate2::Compression::fast(),
474 CompressionLevel::Default => flate2::Compression::default(),
475 CompressionLevel::Best => flate2::Compression::best(),
476 }
477 }
478}
479
480pub fn pack_encrypted_pack_v4(
482 dlc_id: &DlcId,
483 items: &[PackItem],
484 product: &Product,
485 key: &EncryptionKey,
486 block_size: usize,
487) -> Result<Vec<u8>, DlcError> {
488 use aes_gcm::{Aes256Gcm, KeyInit, Nonce, aead::Aead};
489 use flate2::Compression;
490 use flate2::write::GzEncoder;
491 use tar::Builder;
492
493 let cipher = key.with_secret(|kb| {
494 Aes256Gcm::new_from_slice(kb.as_slice()).map_err(|e| DlcError::CryptoError(e.to_string()))
495 })?;
496
497 let mut blocks: Vec<Vec<&PackItem>> = Vec::new();
499 let mut current_block = Vec::new();
500 let mut current_size = 0;
501
502 for item in items {
503 if !current_block.is_empty() && current_size + item.plaintext.len() > block_size {
504 blocks.push(std::mem::take(&mut current_block));
505 current_size = 0;
506 }
507 current_size += item.plaintext.len();
508 current_block.push(item);
509 }
510 if !current_block.is_empty() {
511 blocks.push(current_block);
512 }
513
514 let mut encrypted_blocks = Vec::new();
516 let mut manifest_entries = Vec::new();
517 let mut block_metadatas = Vec::new();
518
519 for (block_id, block_items) in blocks.into_iter().enumerate() {
520 let block_id = block_id as u32;
521 let mut tar_gz = Vec::new();
522 let mut uncompressed_size = 0;
523 {
524 let mut gz = GzEncoder::new(&mut tar_gz, Compression::default());
525 {
526 let mut tar = Builder::new(&mut gz);
527 let mut offset = 0;
528
529 for item in block_items {
530 let mut header = tar::Header::new_gnu();
531 header.set_size(item.plaintext.len() as u64);
532 header.set_mode(0o644);
533 header.set_cksum();
534
535 let path_str = item.path.clone();
536 manifest_entries.push(V4ManifestEntry {
537 path: path_str,
538 original_extension: item.original_extension.clone().unwrap_or_default(),
539 type_path: item.type_path.clone(),
540 block_id,
541 block_offset: offset,
542 size: item.plaintext.len() as u32,
543 });
544
545 tar.append_data(&mut header, &item.path, &item.plaintext[..])
546 .map_err(|e| DlcError::Other(e.to_string()))?;
547
548 let data_len = item.plaintext.len() as u32;
550 let padded_len = (data_len + 511) & !511;
551 offset += 512 + padded_len;
552 uncompressed_size += data_len;
553 }
554 tar.finish().map_err(|e| DlcError::Other(e.to_string()))?;
555 }
556 gz.finish().map_err(|e| DlcError::Other(e.to_string()))?;
557 }
558
559 let nonce_bytes: [u8; 12] = rand::random();
561 let nonce = Nonce::from_slice(&nonce_bytes);
562 let ciphertext = cipher
563 .encrypt(nonce, tar_gz.as_slice())
564 .map_err(|_| DlcError::EncryptionFailed("block encryption failed".into()))?;
565
566 let crc32 = crc32fast::hash(&ciphertext);
567
568 block_metadatas.push(BlockMetadata {
569 block_id,
570 file_offset: 0, encrypted_size: ciphertext.len() as u32,
572 uncompressed_size,
573 nonce: nonce_bytes,
574 crc32,
575 });
576
577 encrypted_blocks.push(ciphertext);
578 }
579
580 let product_str = product.as_ref();
582 let dlc_id_str = dlc_id.to_string();
583
584 let mut out = Vec::new();
585 {
586 let mut writer = PackWriter::new(&mut out);
587
588 let header = PackHeader {
589 version: 4,
590 product: product_str.to_string(),
591 dlc_id: dlc_id_str.clone(),
592 };
593 header.write(&mut writer).map_err(|e| DlcError::Other(e.to_string()))?;
594
595 writer.write_u32(manifest_entries.len() as u32).map_err(|e| DlcError::Other(e.to_string()))?;
597 for entry in &manifest_entries {
598 entry.write_binary(&mut writer).map_err(|e| DlcError::Other(e.to_string()))?;
599 }
600
601 writer.write_u32(block_metadatas.len() as u32).map_err(|e| DlcError::Other(e.to_string()))?;
603 writer.finish().map_err(|e| DlcError::Other(e.to_string()))?;
604 }
605 let metadata_start_pos = out.len();
606 {
607 let mut writer = PackWriter::new(&mut out);
608 for meta in &block_metadatas {
609 meta.write_binary(&mut writer).map_err(|e| DlcError::Other(e.to_string()))?;
610 }
611 writer.finish().map_err(|e| DlcError::Other(e.to_string()))?;
612 }
613
614 for (i, block) in encrypted_blocks.into_iter().enumerate() {
616 let pos = out.len() as u64;
617 block_metadatas[i].file_offset = pos;
618 out.extend_from_slice(&block);
619 }
620
621 {
623 let mut writer_fixed = PackWriter::new(&mut out[metadata_start_pos..]);
624 for meta in &block_metadatas {
625 meta.write_binary(&mut writer_fixed).map_err(|e| DlcError::Other(e.to_string()))?;
626 }
627 writer_fixed.finish().map_err(|e| DlcError::Other(e.to_string()))?;
628 }
629
630 Ok(out)
631}
632
633pub fn parse_encrypted_pack<R: std::io::Read>(
635 reader: R,
636) -> Result<
637 (
638 Product,
639 DlcId,
640 usize,
641 Vec<(String, crate::asset_loader::EncryptedAsset)>,
642 Vec<BlockMetadata>,
643 ),
644 std::io::Error,
645> {
646 use std::io::ErrorKind;
647
648 let mut reader = PackReader::new(reader);
649 let header = PackHeader::read(&mut reader)?;
650 let mut block_metadatas = Vec::new();
651
652 let entries = if header.version == 1 {
653 let entry_count = reader.read_u16()? as usize;
655 let mut out = Vec::with_capacity(entry_count);
656 for _ in 0..entry_count {
657 let path = reader.read_string_u16()?;
658 let original_extension = reader.read_string_u8()?;
659
660 let tlen = reader.read_u16()? as usize;
662 let type_path = if tlen == 0 {
663 None
664 } else {
665 let bytes = reader.read_bytes(tlen)?;
666 let s = String::from_utf8(bytes)
667 .map_err(|e| std::io::Error::new(ErrorKind::InvalidData, e))?;
668 Some(s)
669 };
670
671 let nonce = reader.read_nonce()?;
672 let ciphertext_len = reader.read_u32()? as usize;
673 let ciphertext = reader.read_bytes(ciphertext_len)?.into();
674
675 out.push((
676 path,
677 crate::asset_loader::EncryptedAsset {
678 dlc_id: header.dlc_id.clone(),
679 original_extension,
680 type_path,
681 nonce,
682 ciphertext,
683 block_id: 0,
684 block_offset: 0,
685 size: 0,
686 },
687 ));
688 }
689 out
690 } else if header.version == 4 {
691 let manifest_count = reader.read_u32()? as usize;
693 let mut manifest: Vec<V4ManifestEntry> = Vec::with_capacity(manifest_count);
694 for _ in 0..manifest_count {
695 manifest.push(V4ManifestEntry::read_binary(&mut reader)?);
696 }
697
698 let block_count = reader.read_u32()? as usize;
699 block_metadatas = Vec::with_capacity(block_count);
700 for _ in 0..block_count {
701 block_metadatas.push(BlockMetadata::read_binary(&mut reader)?);
702 }
703
704 let mut out = Vec::with_capacity(manifest.len());
705 for entry in manifest {
706 out.push((
707 entry.path,
708 crate::asset_loader::EncryptedAsset {
709 dlc_id: header.dlc_id.clone(),
710 original_extension: entry.original_extension,
711 type_path: entry.type_path,
712 nonce: [0u8; 12],
713 ciphertext: std::sync::Arc::new([]),
714 block_id: entry.block_id,
715 block_offset: entry.block_offset,
716 size: entry.size,
717 },
718 ));
719 }
720 out
721 } else {
722 let manifest_len = reader.read_u32()? as usize;
724 let manifest_bytes = reader.read_bytes(manifest_len)?;
725 let manifest: Vec<ManifestEntry> = serde_json::from_slice(&manifest_bytes)
726 .map_err(|e| std::io::Error::new(ErrorKind::InvalidData, e))?;
727
728 let mut out = Vec::with_capacity(manifest.len());
729
730 let shared_nonce = reader.read_nonce()?;
731 let shared_ciphertext_len = reader.read_u32()? as usize;
732 let shared_ciphertext: std::sync::Arc<[u8]> = reader.read_bytes(shared_ciphertext_len)?.into();
733
734 for entry in manifest {
735 out.push((
736 entry.path,
737 crate::asset_loader::EncryptedAsset {
738 dlc_id: header.dlc_id.clone(),
739 original_extension: entry.original_extension.unwrap_or_default(),
740 type_path: entry.type_path,
741 nonce: shared_nonce,
742 ciphertext: shared_ciphertext.clone(),
743 block_id: 0,
744 block_offset: 0,
745 size: shared_ciphertext.len() as u32,
746 },
747 ));
748 }
749 out
750 };
751
752 Ok((
753 Product::from(header.product),
754 DlcId::from(header.dlc_id),
755 header.version as usize,
756 entries,
757 block_metadatas,
758 ))
759}
760
761pub fn pack_encrypted_pack(
769 dlc_id: &DlcId,
770 items: &[PackItem],
771 product: &Product,
772 key: &EncryptionKey,
773) -> Result<Vec<u8>, DlcError> {
774 pack_encrypted_pack_v4(dlc_id, items, product, key, DEFAULT_BLOCK_SIZE)
775}
776
777#[cfg(test)]
778mod tests {
779 use super::*;
780
781 #[test]
782 fn forbidden_list_contains_known() {
783 assert!(is_forbidden_extension("exe"));
784 assert!(is_forbidden_extension("EXE"));
785 assert!(!is_forbidden_extension("png"));
786 }
787
788 #[test]
789 fn manifest_roundtrip() {
790 let item = crate::PackItem::new("foo.txt", b"hello" as &[u8]).unwrap();
791 let entry = ManifestEntry::from_pack_item(&item);
792 let bytes = serde_json::to_vec(&entry).unwrap();
793 let back: ManifestEntry = serde_json::from_slice(&bytes).unwrap();
794 assert_eq!(entry.path, back.path);
795 }
796}