Skip to main content

s25/
writer.rs

1use std::fs::File;
2use std::io::{BufWriter, Seek, SeekFrom, Write};
3use std::path::Path;
4
5use std::collections::BTreeMap;
6
7use crate::reader::{S25Image, S25ImageMetadata};
8use crate::{Error, Result};
9
10/// A writer for .S25 format.
11pub struct S25Writer<A = File>
12where
13    A: Write,
14{
15    file: BufWriter<A>,
16    entries: BTreeMap<i32, S25ImageBuffer>,
17}
18
19#[derive(Clone)]
20struct S25ImageBuffer {
21    metadata: S25ImageMetadata,
22    compressed: (Vec<u32>, Vec<u8>),
23}
24
25impl S25Writer {
26    /// Creates a S25 writer.
27    pub fn create<P: AsRef<Path>>(path: P) -> Result<Self> {
28        Ok(Self {
29            file: BufWriter::new(File::create(path)?),
30            entries: BTreeMap::new(),
31        })
32    }
33}
34
35impl<T> From<T> for S25Writer<T>
36where
37    T: Write,
38{
39    fn from(t: T) -> Self {
40        Self {
41            file: BufWriter::new(t),
42            entries: BTreeMap::new(),
43        }
44    }
45}
46
47use std::io::IntoInnerError;
48
49impl<T> S25Writer<T>
50where
51    T: Write + Seek,
52{
53    /// Writes an S25 archive.
54    pub fn write(&mut self) -> Result<()> {
55        const S25_MAGIC: &[u8; 4] = b"S25\0";
56
57        let mut buf_table: Vec<Option<S25ImageBuffer>> = vec![];
58        let entries = std::mem::take(&mut self.entries);
59
60        for entries in entries {
61            let entry_no = entries.0 as usize;
62
63            if buf_table.len() <= entry_no as usize {
64                buf_table.resize(entry_no + 1, None);
65            }
66
67            buf_table[entry_no] = Some(entries.1);
68        }
69
70        let mut entry_table = vec![0i32; buf_table.len()];
71
72        // write header
73        self.file.seek(SeekFrom::Start(0))?;
74        self.file.write_all(S25_MAGIC)?;
75
76        // write total entries
77        self.file
78            .write_all(&(entry_table.len() as i32).to_le_bytes())?;
79
80        // skip header (8 bytes) + entry table (4*entries bytes)
81        self.file
82            .seek(SeekFrom::Start(8 + entry_table.len() as u64 * 4))?;
83
84        for (i, entry) in buf_table.into_iter().enumerate() {
85            if let Some(entry) = entry {
86                let head = self.file.seek(SeekFrom::Current(0))?;
87                entry_table[i] = head as i32;
88
89                // write metadata
90                self.file.write_all(&entry.metadata.width.to_le_bytes())?;
91                self.file.write_all(&entry.metadata.height.to_le_bytes())?;
92                self.file
93                    .write_all(&entry.metadata.offset_x.to_le_bytes())?;
94                self.file
95                    .write_all(&entry.metadata.offset_y.to_le_bytes())?;
96                self.file.write_all(&(0i32).to_le_bytes())?;
97
98                // write compressed image
99                let img_head = (head + 0x14) as u32;
100                for &offset in &entry.compressed.0 {
101                    self.file.write_all(&(offset + img_head).to_le_bytes())?;
102                }
103
104                self.file.write_all(&entry.compressed.1)?;
105            }
106        }
107
108        // go back to entry table
109        self.file.seek(SeekFrom::Start(8))?;
110        for pos in entry_table {
111            self.file.write_all(&pos.to_le_bytes())?;
112        }
113
114        Ok(())
115    }
116
117    /// Unwraps the S25Writer and returns the underlying writer.
118    pub fn into_inner(self) -> std::result::Result<T, IntoInnerError<BufWriter<T>>> {
119        self.file.into_inner()
120    }
121}
122
123impl<T> S25Writer<T>
124where
125    T: Write,
126{
127    /// Adds an image entry to an S25 archive.
128    pub fn add_entry(&mut self, entry_no: i32, image: &S25Image) -> Result<()> {
129        let metadata = image.metadata.clone();
130
131        assert!(metadata.width >= 0);
132        assert!(metadata.height >= 0);
133
134        let compressed = compress_image(
135            &image.bgra_buffer,
136            metadata.width as usize,
137            metadata.height as usize,
138        )?;
139
140        self.entries.insert(
141            entry_no as i32,
142            S25ImageBuffer {
143                metadata,
144                compressed,
145            },
146        );
147
148        Ok(())
149    }
150}
151
152// const METHOD_BGR: u16 = 2;
153// const METHOD_BGR_FILL: u16 = 3;
154const METHOD_ABGR: u16 = 4;
155// const METHOD_ABGR_FILL: u16 = 5;
156
157fn compress_image(rgba_image: &[u8], width: usize, height: usize) -> Result<(Vec<u32>, Vec<u8>)> {
158    use crate::utils;
159
160    assert_eq!(rgba_image.len(), width * height * 4);
161
162    let mut row_offsets = vec![0u32; height];
163
164    let mut compress_buf = vec![];
165    let mut row_buf = vec![];
166
167    let row_data_offset = row_offsets.len() * 4;
168
169    for y in 0..height {
170        row_offsets[y] = (row_data_offset + compress_buf.len()) as u32;
171
172        let head = y * width * 4;
173
174        // TODO: implement PackBits encoding & RGB (no-alpha) support
175        if width <= 0x7ff {
176            utils::io::push_i16(
177                &mut row_buf,
178                encode_count(width as u16, METHOD_ABGR, 0) as i16,
179            );
180        } else if width <= 0xffffffff {
181            // extended count
182            utils::io::push_i16(&mut row_buf, encode_count(0, METHOD_ABGR, 0) as i16);
183            utils::io::push_i32(&mut row_buf, width as i32);
184        } else {
185            return Err(Error::CompressionFailed);
186        }
187
188        for i in 0..width {
189            let offset = i * 4 + head;
190
191            row_buf.push(rgba_image[offset + 3]);
192            row_buf.push(rgba_image[offset + 2]);
193            row_buf.push(rgba_image[offset + 1]);
194            row_buf.push(rgba_image[offset + 0]);
195        }
196
197        // write row length
198        if row_buf.len() <= 0x7fff {
199            utils::io::push_i16(&mut compress_buf, row_buf.len() as i16);
200        } else {
201            // row is too big
202            return Err(Error::CompressionFailed);
203        }
204
205        // write row data
206        compress_buf.append(&mut row_buf);
207    }
208
209    Ok((row_offsets, compress_buf))
210}
211
212fn encode_count(count: u16, method: u16, skip: u16) -> u16 {
213    assert!(count <= 0x7ff);
214    count & 0x7ff | (method << 13) | ((skip & 0x03) << 11)
215}
216
217#[test]
218fn rewrite_susuko() {
219    use crate::S25Archive;
220
221    let mut writer = S25Writer::create("../test/susuko.s25").unwrap();
222    let mut image = S25Archive::open("../test/SUSUKO_01LL.S25").unwrap();
223
224    for i in 0..image.total_entries() {
225        if let Ok(image) = image.load_image(i) {
226            writer.add_entry(i as i32, &image).unwrap();
227        }
228    }
229
230    writer.write().unwrap();
231    drop(writer);
232
233    let mut image2 = S25Archive::open("../test/susuko.s25").unwrap();
234    for i in 0..image.total_entries() {
235        let meta1 = image.load_image(i);
236        let meta2 = image2.load_image(i);
237
238        if let (Ok(mut meta1), Ok(mut meta2)) = (meta1, meta2) {
239            meta1.metadata.head = 0;
240            meta2.metadata.head = 0;
241
242            assert_eq!(meta1.metadata, meta2.metadata, "metadata wrong at: {}", i);
243            assert_eq!(
244                meta1.bgra_buffer, meta2.bgra_buffer,
245                "decode wrong at: {}",
246                i
247            );
248        }
249    }
250}