1use flate2::FlushDecompress;
2use std::cmp::min;
3use std::io::{self, ErrorKind};
4use zerocopy::{
5 FromBytes, Immutable, IntoBytes, KnownLayout,
6 little_endian::{U32, U64},
7};
8
9use crate::image::Image;
10use snafu::{Snafu, ensure};
11
12#[derive(Clone, Copy, FromBytes, KnownLayout, Immutable)]
14#[repr(C)]
15struct PfscHeader {
16 magic: [u8; 4],
18 _unknown_04: U32,
20 _unknown_08: U32,
22 block_size: U32,
24 block_size2: U64,
26 block_offsets: U64,
28 _unknown_20: U64,
30 data_length: U64,
32}
33
34const PFSC_MAGIC: &[u8; 4] = b"PFSC";
35
36#[derive(Debug, Snafu)]
38#[non_exhaustive]
39pub enum OpenError {
40 #[snafu(display("i/o failed"))]
41 IoFailed { source: std::io::Error },
42
43 #[snafu(display("data too small"))]
44 TooSmall,
45
46 #[snafu(display("invalid magic"))]
47 InvalidMagic,
48
49 #[snafu(display("cannot read block mapping"))]
50 ReadBlockMappingFailed { source: std::io::Error },
51}
52
53pub struct PfscImage<I: Image> {
75 source: I,
76 block_size: u32,
77 original_block_size: u64,
78 compressed_blocks: Vec<u64>,
79 original_size: u64,
80}
81
82impl<I: Image> std::fmt::Debug for PfscImage<I> {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 f.debug_struct("PfscImage")
85 .field("block_size", &self.block_size)
86 .field("original_block_size", &self.original_block_size)
87 .field("original_size", &self.original_size)
88 .finish_non_exhaustive()
89 }
90}
91
92impl<I: Image> PfscImage<I> {
93 pub fn open(source: I) -> Result<Self, OpenError> {
97 let mut header_buf = [0u8; size_of::<PfscHeader>()];
99
100 source.read_exact_at(0, &mut header_buf).map_err(|e| {
101 if e.kind() == ErrorKind::UnexpectedEof {
102 OpenError::TooSmall
103 } else {
104 OpenError::IoFailed { source: e }
105 }
106 })?;
107
108 let header =
109 PfscHeader::read_from_bytes(&header_buf).expect("header buffer is correctly sized");
110
111 ensure!(&header.magic == PFSC_MAGIC, InvalidMagicSnafu);
112
113 let block_size = header.block_size.get();
114 let original_block_size = header.block_size2.get();
115 let block_offsets_offset = header.block_offsets.get();
116 let original_size = header.data_length.get();
117
118 let original_block_count = original_size / original_block_size + 1;
120 let mut compressed_blocks: Vec<u64> = vec![0; original_block_count as usize];
121
122 source
123 .read_exact_at(
124 block_offsets_offset,
125 compressed_blocks.as_mut_slice().as_mut_bytes(),
126 )
127 .map_err(|e| OpenError::ReadBlockMappingFailed { source: e })?;
128
129 Ok(Self {
130 source,
131 block_size,
132 original_block_size,
133 compressed_blocks,
134 original_size,
135 })
136 }
137
138 #[must_use]
140 pub fn decompressed_len(&self) -> u64 {
141 self.original_size
142 }
143
144 fn decompress_block(&self, num: u64, out: &mut [u8]) -> io::Result<()> {
148 debug_assert_eq!(out.len(), self.block_size as usize);
149
150 let end = match self.compressed_blocks.get(num as usize + 1) {
152 Some(&v) => v,
153 None => return Err(io::Error::from(ErrorKind::InvalidInput)),
154 };
155
156 let offset = self.compressed_blocks[num as usize];
157 let size = end - offset;
158
159 match size.cmp(&self.original_block_size) {
160 std::cmp::Ordering::Less => {
161 let mut compressed_buf = vec![0u8; size as usize];
163 self.source.read_exact_at(offset, &mut compressed_buf)?;
164
165 let mut deflate = flate2::Decompress::new(true);
167
168 let status = match deflate.decompress(&compressed_buf, out, FlushDecompress::Finish)
169 {
170 Ok(v) => v,
171 Err(e) => return Err(io::Error::other(e)),
172 };
173
174 if status != flate2::Status::StreamEnd || deflate.total_out() as usize != out.len()
175 {
176 return Err(io::Error::other(format!(
177 "invalid data on PFSC block #{}",
178 num
179 )));
180 }
181 }
182
183 std::cmp::Ordering::Equal => {
184 self.source.read_exact_at(offset, out)?;
186 }
187
188 std::cmp::Ordering::Greater => {
189 out.fill(0);
191 }
192 }
193
194 Ok(())
195 }
196}
197
198impl<I: Image> Image for PfscImage<I> {
199 fn read_at(&self, offset: u64, buf: &mut [u8]) -> io::Result<usize> {
200 if buf.is_empty() || offset >= self.original_size {
201 return Ok(0);
202 }
203
204 let block_size = self.block_size as u64;
205 let mut copied = 0usize;
206 let mut pos = offset;
207 let mut block_buf = vec![0u8; self.block_size as usize];
208
209 while copied < buf.len() && pos < self.original_size {
210 let block_index = pos / block_size;
212 let offset_in_block = (pos % block_size) as usize;
213
214 self.decompress_block(block_index, &mut block_buf)?;
216
217 let block_end = (block_index + 1) * block_size;
219 let valid_in_block = if block_end > self.original_size {
220 (self.original_size - block_index * block_size) as usize
221 } else {
222 self.block_size as usize
223 };
224
225 let available = valid_in_block - offset_in_block;
227 let n = min(available, buf.len() - copied);
228
229 buf[copied..copied + n]
230 .copy_from_slice(&block_buf[offset_in_block..offset_in_block + n]);
231
232 copied += n;
233 pos += n as u64;
234 }
235
236 Ok(copied)
237 }
238
239 fn len(&self) -> u64 {
240 self.original_size
241 }
242}